Table of Contents

Class SceneServerSystem

Namespace
FishMMO.Server.Implementation.World.SceneServer
Assembly
FishMMO.Server.dll

Manages scene server node services, scene loading/unloading, and heartbeat updates to the world server. Tracks scene instances, handles connection events, and synchronizes scene state with the database. Game logic and Broadcasts run synchronously on the main thread. Database operations are async to avoid blocking the main thread. Results from async DB queries that require main-thread state changes are marshalled via ISceneServerSystemMainThreadQueueData.

[CreateAssetMenu(fileName = "SceneServerSystem", menuName = "FishMMO/Server/SceneServer/Scene Server System", order = 1)]
[RequiresDataContainer(typeof(SceneInstanceMappingData))]
[RequiresDataContainer(typeof(SceneServerRuntimeData))]
[RequiresDataContainer(typeof(SceneServerSystemMainThreadQueueData))]
[RequiresDataContainer(typeof(AsyncWorkerData))]
public class SceneServerSystem : ServerBehaviour, IServerBehaviour<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>, IServerComponent<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>, ISceneServerSystem<NetworkConnection>, IServerBehaviour, IServerComponent
Inheritance
Object
ScriptableObject
SceneServerSystem
Implements
IServerBehaviour<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>
IServerComponent<INetworkManagerWrapper, ServerManager, NetworkConnection, IServerBehaviour>
ISceneServerSystem<NetworkConnection>
Inherited Members
ScriptableObject.SetDirty()
ScriptableObject.CreateInstance<T>()
Object.GetEntityId()
Object.GetInstanceID()
Object.GetHashCode()
Object.InstantiateAsync<T>(T)
Object.InstantiateAsync<T>(T, Transform)
Object.InstantiateAsync<T>(T, Vector3, Quaternion)
Object.InstantiateAsync<T>(T, Transform, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion, Transform)
Object.Instantiate(Object)
Object.Instantiate(Object, Scene)
Object.Instantiate<T>(T, InstantiateParameters)
Object.Instantiate<T>(T, Vector3, Quaternion, InstantiateParameters)
Object.Instantiate(Object, Transform)
Object.Instantiate<T>(T)
Object.Instantiate<T>(T, Vector3, Quaternion)
Object.Instantiate<T>(T, Vector3, Quaternion, Transform)
Object.Instantiate<T>(T, Transform)
Object.Destroy(Object)
Object.DestroyImmediate(Object)
Object.DontDestroyOnLoad(Object)
Object.DestroyObject(Object)
Object.FindObjectsOfType<T>()
Object.FindObjectsByType<T>(FindObjectsSortMode)
Object.FindObjectsByType<T>(FindObjectsInactive, FindObjectsSortMode)
Object.FindObjectOfType<T>()
Object.FindFirstObjectByType<T>()
Object.FindAnyObjectByType<T>()
Object.FindFirstObjectByType<T>(FindObjectsInactive)
Object.FindAnyObjectByType<T>(FindObjectsInactive)
Object.FindObjectsByType<T>()
Object.FindObjectsByType<T>(FindObjectsInactive)
Object.ToString()
Object.name
Object.hideFlags

Remarks

The database row is the authority for a server's lock and shutdown state, never the process. Commands here write the row; every world and scene server reads its own row back on each pulse and adopts what it finds. That is what makes a single in-game command reach processes the player is not connected to — and it means anything else that can write those rows (the Discord bot, a CMS, psql) controls the servers identically, exactly as kick_requests already works for accounts.

This lives on the scene server because that is where players — and therefore admins — are. It is a partial of SceneServerSystem rather than a new behaviour so it needs no scene wiring: the scene server already loads this system.

Properties

PulseRate

Gets or sets the pulse rate for heartbeat updates.

public float PulseRate { get; set; }

Property Value

float

WorldSceneDetailsCache

Cache of world scene details, including max clients per scene.

public WorldSceneDetailsCache WorldSceneDetailsCache { get; }

Property Value

WorldSceneDetailsCache

Methods

AdjustSceneCharacterCount(long, string, long, int)

Adjusts the character count for a specific scene instance.

public void AdjustSceneCharacterCount(long worldServerID, string sceneName, long sceneID, int amount)

Parameters

worldServerID long

World server ID.

sceneName string

Scene name.

sceneID long

Scene row ID identifying the instance.

amount int

Amount to adjust by (+1 or -1).

CloseInstance(long, string)

Returns an instance's occupants to the open world and unloads it.

public void CloseInstance(long sceneID, string reason)

Parameters

sceneID long

Scene row of the instance to close.

reason string

Why it is closing, for diagnostics.

Remarks

The only way an instance may be unloaded. UnloadScene(long) destroys the scene outright, which was safe while the sole caller was the stale sweep — that only ever picks empty scenes — but is not once an instance can be closed with people standing in it. Evicting first sends each of them through the ordinary leave-instance path, so their state is saved, their session claim released and their client re-routed, rather than having the ground removed from under them.

InitializeOnce()

Called once to initialize the scene server system. Registers the server in the database and subscribes to connection and scene events.

public override ServerComponentInitializationStatus InitializeOnce()

Returns

ServerComponentInitializationStatus

InitializeOnceAsync(CancellationToken)

Initializes the system and registers it in the database without blocking the Unity main thread.

public override Task<ServerComponentInitializationStatus> InitializeOnceAsync(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

Returns

Task<ServerComponentInitializationStatus>

Remarks

Awaits here deliberately capture Unity's SynchronizationContext (no ConfigureAwait(false)), so execution resumes on the main thread and the Unity and FishNet APIs used below stay legal.

NoteDeliberateInstanceExit(long, string, long)

Records that an instance's occupant left by choice rather than by losing its connection.

public void NoteDeliberateInstanceExit(long worldServerID, string sceneName, long sceneID)

Parameters

worldServerID long

World server ID.

sceneName string

Instance scene name.

sceneID long

Scene row ID identifying the instance.

Remarks

Called after the departure has been debited from the instance's population, so if this was the last occupant the instance is already empty and the next pulse reclaims it immediately instead of holding it for the idle timeout. See VacatedDeliberately for why the two cases differ.

A missing instance is not an error here, unlike in AdjustSceneCharacterCount(long, string, long, int): the instance may already have been unloaded by the very departure being recorded, and the only consequence of not marking a scene that no longer exists is nothing at all.

OnDeinitialize()

Called when the system is being destroyed. Unsubscribes from events and deletes scene data from the database.

public override void OnDeinitialize()

OnUpdate(float)

Drains the main-thread queue each frame.

protected override void OnUpdate(float deltaTime)

Parameters

deltaTime float

SceneManager_OnUnloadEnd(SceneUnloadEndEventArgs)

Handles scene unload completion events, removing scene mappings and cleaning up. Uses the flat SceneInstanceByHandle map for O(1) lookup per unloaded scene, then removes from both the flat map and the nested WorldScenes hierarchy.

public void SceneManager_OnUnloadEnd(SceneUnloadEndEventArgs args)

Parameters

args SceneUnloadEndEventArgs

Scene unload end event arguments.

TryGetInstanceExpiry(long, out DateTime)

When an instance will close on its own, if it is time-bounded.

public bool TryGetInstanceExpiry(long sceneID, out DateTime expiresUtc)

Parameters

sceneID long

Scene row of the instance.

expiresUtc DateTime

When it closes, if it does.

Returns

bool

false for a scene this server does not host, an open-world scene, or one whose creation time was never recorded — none of which have an expiry to report.

Remarks

The lifetime cap is configuration this system owns, so the answer is derived here rather than by making every caller read the key and repeat the arithmetic — which is how two places end up disagreeing about when a dungeon ends.

TryGetSceneInstanceDetails(long, string, long, out ISceneInstanceDetails)

Attempts to get scene instance details for a given world server, scene name, and handle. Uses the flat SceneInstanceByHandle map for O(1) lookup, then validates the world server and scene name match as a correctness check.

public bool TryGetSceneInstanceDetails(long worldServerID, string sceneName, long sceneID, out ISceneInstanceDetails instanceDetails)

Parameters

worldServerID long

World server ID.

sceneName string

Scene name.

sceneID long
instanceDetails ISceneInstanceDetails

Output instance details.

Returns

bool

True if found, false otherwise.

TryLoadSceneForConnection(NetworkConnection, ISceneInstanceDetails)

Attempts to load a scene for a connection if it is valid and loaded.

public bool TryLoadSceneForConnection(NetworkConnection connection, ISceneInstanceDetails instance)

Parameters

connection NetworkConnection

Network connection.

instance ISceneInstanceDetails

Scene instance details.

Returns

bool

True if scene was loaded for the connection, false otherwise.

UnloadScene(long)

Unloads a scene by handle and removes its details from the database and server.

public void UnloadScene(long sceneID)

Parameters

sceneID long

UnloadSceneForConnection(NetworkConnection, string)

Unloads a scene for a connection by scene name.

public void UnloadSceneForConnection(NetworkConnection connection, string sceneName)

Parameters

connection NetworkConnection

Network connection.

sceneName string

Name of the scene to unload.