Skip to main content

FN Facade

FN (FigNet.Core.FN) is the static entry point for the FigNet core: configuration, connection/socket setup, module loading, payload registration, and handler wiring. It exists identically in concept on both the .NET server and the Unity client, but the two are separate, hand-ported copies of the same source file (changes to one need to be ported to the other by hand). Everything below is copy-verified from FigNet/Core/FigNet.cs.

Constants and core state

public const string VERSION = "v1.3.0";
public const int THREADED_PROCESS_CLIENT_TICK_RATE = 45;

public static List<IServerSocket> Servers { get; }
public static List<IClientSocket> Connections { get; }
public static ILogger Logger { get; set; }
public static PeerCollection PeerCollection { get; }
public static HandlerCollection HandlerCollection { get; }
public static Configuration Settings { get; }
public static bool IsInitialized { get; set; }
public static bool IsMultiThreaded { get; set; } = true;
public static uint ActiveUsers { get; }
  • Servers - all listening server sockets, populated during Initilize/LoadModules.
  • Connections - all outbound client connections (autoconnected peers plus anything added via AddConnectionAndConnect).
  • Settings - the currently loaded Configuration (see Configuration); a default instance exists even before LoadConfig() is called, so code that reads FN.Settings early doesn't null-ref.
  • ActiveUsers - forwards to Internals.ActiveUsers.
public static Action OnSettingsLoaded = () => { };
public static Action OnInitilized = () => { };

Fired at the end of LoadConfig() and Initilize(Configuration) respectively, for code that needs to hook startup without editing the facade itself.

Initialization lifecycle

Startup order is BeforeInit() -> LoadConfig() -> Initilize() -> LoadModules(server).

public static void BeforeInit()

Sets up the empty collections (HandlerCollection, PeerCollection, Connections, Servers, the payload serializer dictionaries), initializes PeerFactory/SocketFactory, installs a DefaultLogger, seeds a fallback Settings (ApplicationType.Server, 30 FPS, console logging) so config-less usage doesn't throw, and registers the two reserved payloads (RegisterIdentity, Ping). Call this once before anything else.

public static void LoadConfig()

Reads ServerConfig.yaml from GetConfigDirectory(), deserializes it into Settings, refreshes the startup log level defaults, and logs a summary of configured modules/games. Fires OnSettingsLoaded. Failures are caught and logged, not thrown.

public static void Initilize()
public static void Initilize(Configuration config)

Initilize() calls Initilize(Settings) then applies SubscribeDebugMessages from config. Initilize(Configuration) does the real work: for ApplicationType.Server, creates and binds an IServerSocket per ServerConfigs entry (via SocketFactory.GetServerSocket) and autoconnects any PeerConfigs with AutoConnect = true; for ApplicationType.Client, autoconnects PeerConfigs only. Fires OnInitilized at the end.

public static void Deinitialize()

No-op if IsInitialized is false. Otherwise stops every server socket, disconnects every client connection, clears PeerCollection, disposes any secure server credentials, and sets IsInitialized = false.

public static string GetConfigDirectory()

Resolves the directory containing ServerConfig.yaml by checking, in order: /app/data/, the current working directory, then the executing assembly's directory - falling back to AppContext.BaseDirectory if none exist.

Connections

public static IClientSocket AddConnectionAndConnect(PeerConfig peerConfig, IClientSocketListener socketListener = null)
public static IClientSocket AddConnectionAndConnect(int config = 0, IClientSocketListener socketListener = null)

Creates a client socket for the given PeerConfig (or by index into Settings.PeerConfigs), optionally binds a listener, connects, adds it to Connections, and returns it. Returns null and logs on failure.

public static void RemoveConnectionAndDisconnect(string name)
public static void RemoveConnectionAndDisconnect(int config = 0)

Finds the matching connection (by Name, or by index into Connections), disconnects it, and removes it from Connections.

public static void BindServerListner(IServerSocketListener listener, string appName = "all")
public static void UnBindServerListner(IServerSocketListener listener, string appName = "all")

Binds/unbinds a socket listener to every server in Servers whose AppName matches (or all of them when appName == "all").

FN.Server (message dispatch by peer)

public static class Server
{
public static void SendMessage(IPeer peer, Message message, DeliveryMethod mode, byte channelId);
public static void SendMessage(List<IPeer> peers, Message message, DeliveryMethod mode, byte channelId);
}

Looks up the owning IServerSocket for a peer (by peer.AppName + peer.Provider) and forwards the send to it. If no matching server (or peer/list is null/empty), the message's pooled payload is returned via Message.Return instead of leaking it. The multi-peer overload retains the payload (message.TryRetainPayload()) for every dispatch after the first, since one Message is being fanned out to multiple sockets. See Messaging for Message/lease semantics.

Payload registration

Payloads must be registered before a msgId can be sent/received. There are two families - the boxing SerializeFunction<T>/DeserializeFunction<T> pair, and the allocation-free SerializeIntoFunction<T>/ DeserializeSpanFunction<T> pair - plus mixed-type overloads where the serialize and deserialize sides use different generic type parameters.

public static void RegisterPayload<T>(ushort msgId, SerializeFunction<T> serializeFunc, DeserializeFunction<T> deserializeFunc);
public static void RegisterPayload<T>(ushort msgId, SerializeIntoFunction<T> serializeFunc, DeserializeSpanFunction<T> deserializeFunc);
public static void RegisterPayload<T, T2>(ushort msgId, SerializeFunction<T> serializeFunc, DeserializeFunction<T2> deserializeFunc);
public static void RegisterPayload<T, T2>(ushort msgId, SerializeIntoFunction<T> serializeFunc, DeserializeSpanFunction<T2> deserializeFunc);

Each overload adds to the corresponding ConcurrentDictionary<ushort, Delegate> (SerializeFuncs/DeserializeFuncs or SerializeIntoFuncs/DeserializeSpanFuncs). If a msgId is already registered on either side, nothing is overwritten and an error is logged ("Payload serializer already registered messageId={msgId}." or the "Fast" variant) - registration is not idempotent-overwrite, it's register-once. On success, the registration is also recorded against the current module load scope (see below) so a hot-unloaded module cleans its payloads up automatically.

public static void UnRegisterPayload(ushort msgId);

Removes msgId from all four dictionaries (TryRemove, so it's a safe no-op if nothing was registered).

Example (mirrors BeforeInit's own reserved-payload registration):

FN.RegisterPayload((ushort)MyMsgId.PlayerJoin,
PlayerJoinModel.Serialize<PlayerJoinModel>,
PlayerJoinModel.Deserialize<PlayerJoinModel>);

Module loading

public static void LoadModules(IServer server);

Iterates FN.Settings.Modules, skipping any with Enabled == false. For each enabled entry it dispatches on module.UsesNewLayout:

  • New layout (manifest-based, Modules/{Name}/manifest.json): delegates to ModuleLoader.Load(server, modulesRoot, module.Name, out error), the same path used for admin-driven hot-load.
  • Legacy layout (co-located DLL + AssemblyName/Type in YAML): loads dependency assemblies, LoadFroms the module's own assembly, instantiates module.Type via Activator.CreateInstance, casts to IModule, calls server.AddModule(instance), and registers it with ModuleLoader.RegisterBuiltIn so the admin plane reports it (and correctly refuses unload/reload/delete on it).

Per-module load failures are caught and logged, not thrown - one bad module doesn't stop the rest from loading. See Modules for the manifest schema and layout details.

Module version registry

public static void RegisterModuleVersion(string name, string version);
public static void UnRegisterModuleVersion(string name);
public static bool TryGetModuleVersion(string name, out string version);
public static IReadOnlyDictionary<string, string> ModuleVersions { get; }

Server-side moduleName -> version map, populated by each module's Load(). Used at connect time to validate a client's declared CompatibleServerVersions ranges; a module the server never registered is not gated (intersection-only check). UnRegisterModuleVersion removes the entry on unload so a reload can't leave a stale version behind.

public static ModuleRegistrar CurrentRegistrar { get; }

The ambient registrar for the module currently being loaded (thread-static; null outside a load). Payload and module-version registrations made while a registrar is active are recorded into it so a hot-unloaded/collectible module can be cleaned up completely; process-lifetime (first-party) registrations made outside a load are not tracked here.

Logging

public static void EnsureLogger(ILogger logger);

Sets FN.Logger and calls logger.SetUp(false, string.Empty) only if Logger is currently null - the first non-null logger wins. Prefer this over assigning FN.Logger directly, which would re-subscribe LogMessageReceived and duplicate log lines.

public static void SetLogLevel(LogType level);
public static LogType GetActiveLogLevel();
public static LogType GetStartupLogLevel();
public static LogType GetEffectiveLogLevel(LogType configuredLevel);
public static bool HasRuntimeLogLevelOverride { get; }
public static bool IsLogEnabled(LogType level);
public static void RefreshStartupLogLevelDefaults(Configuration settings);
public static Dictionary<string, string> GetStartupServerLogLevels();
public static string GetStartupServerLogLevelSummary();

SetLogLevel installs a runtime override (takes precedence over config-driven levels) and immediately applies it. GetEffectiveLogLevel(configuredLevel) returns the runtime override if one is set, otherwise the level passed in. GetStartupServerLogLevels()/GetStartupServerLogLevelSummary() summarize the logging level each configured ServerConfig started with (useful for an admin/diagnostics view). See Logging for the full logging model.

public static void SubscribeToDetailedLog(ushort messageId);
public static void UnSubscribeToDetailedLog(ushort messageId);
public static Action<string, string, LogType> LogMessageReceived;

Adds/removes a messageId from Internals.SubscribedMessages for detailed per-message logging. See Handlers/Commands for where message handling itself is registered (FN.HandlerCollection).

Security

public static SecureServerCredentials GetOrCreateSecureServerCredentials();

Lazily creates (via SecureServerCredentials.Create()) and caches the process's secure server credentials; disposed and cleared on Deinitialize()/Initilize(Configuration).

Notes on what's intentionally skipped

The TryGetSerializeFunc/TryGetDeserializeFunc/TryGetSerializeIntoFunc/TryGetDeserializeSpanFunc lookups, GetServer, LoadModuleFromManifest/LoadModuleLegacy, ApplyResolvedLogLevel, ResolveStartupLogLevel, AccumulateMinLogLevel, and BuildServerLogLevelKey are all internal/private plumbing used by the handler pipeline and module loader internally - they are not part of the public API surface application code calls directly, so they're omitted here.