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 duringInitilize/LoadModules.Connections- all outbound client connections (autoconnected peers plus anything added viaAddConnectionAndConnect).Settings- the currently loadedConfiguration(see Configuration); a default instance exists even beforeLoadConfig()is called, so code that readsFN.Settingsearly doesn't null-ref.ActiveUsers- forwards toInternals.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 toModuleLoader.Load(server, modulesRoot, module.Name, out error), the same path used for admin-driven hot-load. - Legacy layout (co-located DLL +
AssemblyName/Typein YAML): loads dependency assemblies,LoadFroms the module's own assembly, instantiatesmodule.TypeviaActivator.CreateInstance, casts toIModule, callsserver.AddModule(instance), and registers it withModuleLoader.RegisterBuiltInso 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.