using System; using System.Collections.Generic; using Server.Commands; namespace Server.Custom.Bridge { /// /// Lifecycle wiring. Boot order (Server/Main.cs:544-562, all on the Core thread): /// /// Configure() -> World.Load() -> Initialize() -> EventSink.ServerStarted /// /// Config is read in Configure. Handlers are attached in Initialize. The socket opens on /// ServerStarted, once the world is actually there to describe. /// /// EventSink.Shutdown does NOT fire on a crash (Server/Main.cs:198,313), so the sidecar /// must treat socket EOF as normal and re-handshake rather than waiting for a goodbye. /// public static class BridgeBoot { private static readonly Dictionary>> _handlers = new Dictionary>>(StringComparer.Ordinal); /// /// Identifies this run of the shard. It is stable across sidecar reconnects and changes /// on every shard restart, which is how the sidecar tells "I reconnected" (keep my /// cached state) from "the shard restarted" (discard it). /// private static string _bootId; public static void Configure() { BridgeConfig.Configure(); } public static void Initialize() { if (!BridgeConfig.Enabled) { Console.WriteLine("[Bridge] disabled by config"); return; } CommandSystem.Register("bridge", AccessLevel.Administrator, Bridge_OnCommand); RegisterHandler("ping", OnPing); BridgeLink.InboundLine += OnInboundLine; BridgeLink.Connected_Core += EmitHello; EventSink.ServerStarted += OnServerStarted; EventSink.Shutdown += OnShutdown; EventSink.Crashed += OnCrashed; Console.WriteLine("[Bridge] {0}", BridgeConfig.Describe()); } /// Handlers run on the Core thread. They may touch the world freely. public static void RegisterHandler(string kind, Action> handler) { _handlers[kind] = handler; } private static void OnServerStarted() { _bootId = Guid.NewGuid().ToString("N"); BridgeLink.Start(); } /// /// Core thread, once per connection. The sidecar restarts independently of the shard, /// so this is sent on every connect rather than once at boot — otherwise a sidecar that /// came up second would never learn which shard it is talking to. /// private static void EmitHello() { BridgeLink.Emit(BridgeJson.Begin("server.hello") .Str("shard", Server.Misc.ServerList.ServerName) .Str("bootId", _bootId) .Num("connects", BridgeLink.Connects) .Num("items", World.Items.Count) .Num("mobiles", World.Mobiles.Count) .Num("accounts", Accounting.Accounts.Count) .End()); } private static void OnShutdown(ShutdownEventArgs e) { BridgeLink.Emit(BridgeJson.Begin("server.shutdown").End()); // Stop() joins the link thread for up to 2s, which gives the writer a chance to drain // the goodbye. Best effort: the sidecar must not depend on receiving it. BridgeLink.Stop(); } private static void OnCrashed(CrashedEventArgs e) { try { BridgeLink.Emit(BridgeJson.Begin("server.crashed") .Str("error", e.Exception == null ? null : e.Exception.Message) .End()); BridgeLink.Stop(); } catch { // The process is already going down. Never make a crash worse. } } /// Core thread, one call per inbound line. private static void OnInboundLine(string line) { var obj = BridgeJson.Parse(line); if (obj == null) { Console.WriteLine("[Bridge] malformed inbound line, ignoring"); return; } var kind = BridgeJson.GetString(obj, "kind"); if (kind == null) return; Action> handler; if (!_handlers.TryGetValue(kind, out handler)) { Console.WriteLine("[Bridge] no handler for inbound kind '{0}'", kind); return; } handler(obj); } private static void OnPing(Dictionary o) { var sb = BridgeJson.Begin("pong"); var id = BridgeJson.GetString(o, "id"); if (id != null) sb.Str("id", id); BridgeLink.Emit(sb.End()); } [Usage("bridge [status | reload | ping | sweepnow]")] [Description("Inspects and controls the sidecar link.")] private static void Bridge_OnCommand(CommandEventArgs e) { var arg = e.Length > 0 ? e.GetString(0).ToLowerInvariant() : "status"; switch (arg) { case "reload": BridgeConfig.Load(); BridgeSweeps.Rearm(); BridgePages.Rearm(); BridgeChamps.Rearm(); BridgeSocial.Rearm(); BridgeGovernance.Rearm(); BridgePresence.Rearm(); BridgeHousing.Rearm(); // Not a sweep, so it has nothing to re-arm — but an operator who just edited a // .cfg wants the change on the site now, not after a shard restart. BridgeRuleset.Emit(); e.Mobile.SendMessage("Bridge: {0}", BridgeConfig.Describe()); e.Mobile.SendMessage("Bridge: sweeps re-armed; ruleset re-emitted; endpoint changes take effect on reconnect."); break; case "ping": BridgeLink.Emit(BridgeJson.Begin("ping").End()); e.Mobile.SendMessage("Bridge: ping queued."); break; case "sweepnow": BridgeSweeps.SweepOnce(); BridgeChamps.SweepOnce(); BridgeSocial.SweepOnce(); BridgeGovernance.SweepOnce(); BridgePresence.SweepOnce(); BridgeHousing.SweepOnce(); e.Mobile.SendMessage("Bridge: ran one sweep of each stream."); e.Mobile.SendMessage("Bridge: {0}", BridgeSweeps.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeChamps.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeSocial.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeGovernance.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgePresence.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeHousing.Status()); break; default: e.Mobile.SendMessage("Bridge: {0}", BridgeConfig.Describe()); e.Mobile.SendMessage( "Bridge: connected={0} depth={1} sent={2} dropped={3} received={4} connects={5} writeErrors={6}", BridgeLink.Connected, BridgeLink.Depth, BridgeLink.Sent, BridgeLink.Dropped, BridgeLink.Received, BridgeLink.Connects, BridgeLink.WriteErrors); e.Mobile.SendMessage("Bridge: {0}", BridgeSweeps.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeChamps.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeSocial.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeGovernance.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgePresence.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeHousing.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgePages.Status()); e.Mobile.SendMessage("Bridge: {0}", BridgeRuleset.Status()); break; } } } }