Skip to main content
Version: v3

GSIManagerBase

Important Attention Reminder

Coding Style wiki


GSIManagerBase<T> is the abstract base class for game stage managers in GSIFrame (Game Stage Integration), following the FSM concept. It manages the registration, lookup, switching, and update driving of GSIBase stages. After inheriting from it, everything is accessed through static Default APIs on the subclass name. A game typically needs only one manager subclass.

NamespaceOxGFrame.GSIFrame
Typepublic abstract class
SourceGSIManagerBase.cs
using OxGFrame.GSIFrame;

Reminder If your project uses hot updates, it is recommended to keep two separate managers (such as AotGameStageManager and HotfixGameStageManager).

Declaration

public abstract class GSIManagerBase<T> where T : GSIManagerBase<T>, new()

Generic Parameters

Generic ParameterDescription
TThe manager subclass itself (CRTP self-referencing constraint); it must have a parameterless constructor (new()).

When inheriting, pass the subclass itself as the generic parameter:

public class GSIManagerExample : GSIManagerBase<GSIManagerExample> { /* ... */ }

How It Works

Singleton & Driving

GSIManagerBase<T> has a built-in thread-safe lazy singleton (double-checked locking). The first call to any static method automatically creates the T instance and runs its constructor (the base constructor initializes the stage cache). All Default APIs are forwarded through this singleton to the corresponding instance methods.

Important
  • GSIManagerBase<T> is a plain C# class (not a MonoBehaviour) and never updates on its own. It must be driven by your main entry MonoBehaviour, calling DriveStart in Start() and DriveUpdate in Update().
  • Never call any Default API static method inside the subclass constructor — it recurses through GetInstance() and causes a StackOverflow (infinite loop). Use the instance methods (such as AddGameStage) in the constructor instead.

Stage Registration & IDs

  • Each stage is registered in the cache under a unique int id. The generic overloads without an id use typeof(U).GetHashCode() as the id (so only one instance per type can be registered this way).
  • The overloads with an id let you assign custom ids, allowing multiple instances of the same type.
  • Registering a duplicate id logs a warning and is skipped — the existing stage is not overwritten.
  • On registration, the manager automatically calls gameStage.SetId(id) to assign the stage identifier.

Stage Change Flow

ModeTakes effectBehavior
Normal change (force = false)On the next driven updateOnly records the target id. On the next DriveUpdate, when the id change is detected, it runs "old stage OnExit → update current id → new stage initialization flow" in order. Changing to the current stage is not allowed (a warning is logged and nothing happens).
Forced change (force = true)ImmediatelyImmediately runs "old stage OnExit → update current id → new stage initialization flow" in order. No same-stage check is performed, so it can be used to re-enter the current stage.
Attention
  • For the new stage's initialization flow (OnCreate / OnEnter / refresh enabling), see GSIBase Lifecycle & Call Sequence.
  • When changing to an unregistered id, the old stage still runs OnExit, then an error is logged and the current stage becomes null.

Inheritance Example

using OxGFrame.GSIFrame;
using UnityEngine;

// Custom game stage manager: pass the subclass itself as the generic parameter (CRTP)
public class GSIManagerExample : GSIManagerBase<GSIManagerExample>
{
public GSIManagerExample()
{
// Register the game stages in the constructor (instance methods only — never Default APIs here)
this.AddGameStage<StartupStageExample>();
this.AddGameStage<LogoStageExample>();
this.AddGameStage<PatchStageExample>();
this.AddGameStage<LoginStageExample>();
this.AddGameStage<EnterStageExample>();
}

public override void OnStart()
{
// Start the first game stage
this.ChangeGameStage<StartupStageExample>();
}

public override void OnUpdate(float dt = 0.0f)
{
base.OnUpdate(dt); // Must be called, otherwise stages will not switch or update
}
}

// Main entry MonoBehaviour that drives the manager
public class Main : MonoBehaviour
{
private void Start()
{
GSIManagerExample.DriveStart();
}

private void Update()
{
GSIManagerExample.DriveUpdate(Time.deltaTime);
}
}

After that, switch stages from anywhere through the Default APIs:

// Normal change (executed on the next driven update)
GSIManagerExample.ChangeStage<LoginStageExample>();

// Forced change (executed immediately)
GSIManagerExample.ChangeStage<LoginStageExample>(true);

Reminder You can quickly create a manager subclass from the template via the Project window context menu: Create → OxGFrame → GSI Frame → Template Scripts → Template GSIManager.cs (Game Stage Manager).

Member Overview

Static Methods (Default API)

Called directly on the subclass name after inheriting (e.g. GSIManagerExample.ChangeStage<U>()).

MethodDescription
GetCurrentIdGets the current stage id.
GetStage<U>Gets a registered stage instance.
AddStageCreates and registers a game stage.
DeleteStageRemoves a registered stage from the cache.
ChangeStageChanges the game stage (normal / forced).
DriveStartDrives startup (call in the main entry's Start()).
DriveUpdateDrives updates (call in the main entry's Update()).
Start / UpdateObsolete legacy driving APIs.

Overridable Methods (virtual)

MethodDescription
OnStartCalled by DriveStart; override it to start the first stage.
OnUpdateCalled by DriveUpdate every frame; runs stage change detection and refreshing by default.

Instance Methods

For use inside the subclass (e.g. registration in the constructor, starting a stage in OnStart).

MethodDescription
GetCurrentGameStageIdGets the current stage id (instance version).
GetGameStage<U>Gets a registered stage instance (instance version).
AddGameStageRegisters a game stage (instance version).
DeleteGameStageRemoves a registered stage (instance version).
ChangeGameStageNormal stage change (instance version).
ChangeGameStageForceImmediate forced stage change (instance version).

Protected Members (available to subclasses)

MemberTypeDescription
_dictGameStageDictionary<int, GSIBase>Stage cache.
_incomingIdintId of the incoming (target) stage.
_currentIdintCurrent stage id (read-only property).
_currentGameStageGSIBaseCurrent stage instance (read-only property).
GetInstance()static TGets the manager singleton (lazily created, thread-safe).
UpdateGameStage(float dt = 0.0f)voidDetects stage changes and refreshes the current stage (called by OnUpdate).
InitGameStage()voidFetches the stage for the current id and starts its initialization flow.
ReleaseGameStage()voidCalls the current stage's OnExit.

GetCurrentId

public static int GetCurrentId()

Returns

int — the current stage id; 0 if no stage change has happened yet.

Description

Gets the id of the currently running stage.


GetStage<U>

public static U GetStage<U>() where U : GSIBase
public static U GetStage<U>(int id) where U : GSIBase

Parameters

ParameterTypeDescription
idintStage id. When omitted, typeof(U).GetHashCode() is used for the lookup.

Returns

U — the matching stage instance; null if not found.

Description

Gets a registered stage instance from the cache. The parameterless overload looks up by type hash, so it only works for stages registered without an explicit id; for stages registered with a custom id, use GetStage<U>(int id).

Example

var loginStage = GSIManagerExample.GetStage<LoginStageExample>();

AddStage

public static void AddStage<U>() where U : GSIBase, new()
public static void AddStage<U>(int id) where U : GSIBase, new()
public static void AddStage(int id, GSIBase gameStage)

Parameters

ParameterTypeDescription
idintStage id. When omitted, typeof(U).GetHashCode() is used as the id.
gameStageGSIBaseAn existing stage instance (registered under the given id).

Description

Creates (new U()) and registers a game stage in the manager's cache, or registers an existing instance under the given id. SetId(id) is called automatically on registration to assign the stage identifier.

Attention Registering a duplicate id logs a warning and is skipped; the existing stage is not overwritten.

Example

// Register with the type hash as the id
GSIManagerExample.AddStage<FightStageExample>();

// Register an existing instance under a custom id
GSIManagerExample.AddStage(0x01, new FightStageExample());

DeleteStage

public static void DeleteStage<U>() where U : GSIBase
public static void DeleteStage(int id)

Parameters

ParameterTypeDescription
idintStage id. When omitted, typeof(U).GetHashCode() is used for the lookup.

Description

Removes a registered stage from the cache; does nothing if not found.

Attention This only removes the stage from the cache; the stage's OnExit is not triggered.


ChangeStage

public static void ChangeStage<U>(bool force = false) where U : GSIBase
public static void ChangeStage(int id, bool force = false)

Parameters

ParameterTypeDescription
idintTarget stage id. The generic overload uses typeof(U).GetHashCode() as the target id.
forceboolWhether to force the change immediately.
Default: false (normal change)

Description

Changes the game stage:

  • Normal change (force = false): records the target id and performs the change on the next driven update; changing to the current stage is not allowed (a warning is logged and nothing happens).
  • Forced change (force = true): immediately runs "old stage OnExit → update current id → new stage initialization flow" in order; no same-stage check is performed, so it can be used to re-enter the current stage.

See Stage Change Flow and GSIBase Lifecycle & Call Sequence for details.

Example

// Normal change (executed on the next driven update)
GSIManagerExample.ChangeStage<EnterStageExample>();

// Forced change (executed immediately; can also re-enter the current stage)
GSIManagerExample.ChangeStage<EnterStageExample>(true);

DriveStart

public static void DriveStart()

Description

Drives startup by forwarding to the singleton's OnStart. Call it once in the main entry MonoBehaviour's Start(). The first call automatically creates the manager singleton (running the constructor, which performs stage registration).

Example

private void Start()
{
GSIManagerExample.DriveStart();
}

DriveUpdate

public static void DriveUpdate(float dt = 0.0f)

Parameters

ParameterTypeDescription
dtfloatDelta time (usually Time.deltaTime).
Default: 0.0f

Description

Drives updates by forwarding to the singleton's OnUpdate, which performs stage change detection and refreshes the current stage's OnUpdate. Call it every frame in the main entry MonoBehaviour's Update().

Example

private void Update()
{
GSIManagerExample.DriveUpdate(Time.deltaTime);
}

Start / Update (Obsolete)

[Obsolete("Use DriveStart instead.")]
public static void Start()

[Obsolete("Use DriveUpdate instead.")]
public static void Update(float dt = 0.0f)

Description

Legacy driving APIs, behaving the same as DriveStart and DriveUpdate respectively.

Attention Marked [Obsolete] — use DriveStart / DriveUpdate instead.


OnStart

public virtual void OnStart()

Description

Called by DriveStart. The base implementation is empty; in your override, this is typically where you call ChangeGameStage to start the first game stage.


OnUpdate

public virtual void OnUpdate(float dt)

Parameters

ParameterTypeDescription
dtfloatDelta time, passed in by DriveUpdate.

Description

Called every frame by DriveUpdate. The base implementation runs UpdateGameStage(dt): it detects stage changes (normal changes take effect here) and calls the current stage's OnUpdate while its runUpdate is enabled.

Important When overriding, you must call base.OnUpdate(dt), otherwise stages will not switch or update.


GetCurrentGameStageId

public int GetCurrentGameStageId()

Returns

int — the current stage id; 0 if no stage change has happened yet.

Description

Instance version of GetCurrentId, for use inside the subclass.


GetGameStage<U>

public U GetGameStage<U>() where U : GSIBase
public U GetGameStage<U>(int id) where U : GSIBase

Parameters

ParameterTypeDescription
idintStage id. When omitted, typeof(U).GetHashCode() is used for the lookup.

Returns

U — the matching stage instance; null if not found.

Description

Instance version of GetStage<U>, for use inside the subclass.


AddGameStage

public void AddGameStage<U>() where U : GSIBase, new()
public void AddGameStage<U>(int id) where U : GSIBase, new()
public void AddGameStage(int id, GSIBase gameStage)

Parameters

ParameterTypeDescription
idintStage id. When omitted, typeof(U).GetHashCode() is used as the id.
gameStageGSIBaseAn existing stage instance (registered under the given id).

Description

Instance version of AddStage. Use it to register stages in the subclass constructor (where static Default APIs must not be called). gameStage.SetId(id) is called automatically on registration; registering a duplicate id logs a warning and is skipped. Passing a null stage instance logs an error and is skipped as well.

Example

public GSIManagerExample()
{
this.AddGameStage<StartupStageExample>();
}

DeleteGameStage

public void DeleteGameStage<U>() where U : GSIBase
public void DeleteGameStage(int id)

Parameters

ParameterTypeDescription
idintStage id. When omitted, typeof(U).GetHashCode() is used for the lookup.

Description

Instance version of DeleteStage; does nothing if not found. Deleting the currently running stage is not allowed (a warning is logged and the call is skipped).


ChangeGameStage

public void ChangeGameStage<U>() where U : GSIBase
public void ChangeGameStage(int id)

Parameters

ParameterTypeDescription
idintTarget stage id. The generic overload uses typeof(U).GetHashCode() as the target id.

Description

Instance version of the normal stage change: records the target id and performs the change on the next driven update; changing to the current stage is not allowed, and if the target stage does not exist an error is logged and the change is cancelled. Typically called in OnStart to start the first stage.


ChangeGameStageForce

public void ChangeGameStageForce<U>() where U : GSIBase
public void ChangeGameStageForce(int id)

Parameters

ParameterTypeDescription
idintTarget stage id. The generic overload uses typeof(U).GetHashCode() as the target id.

Description

Instance version of the forced stage change: immediately runs "old stage OnExit → update current id → new stage initialization flow" in order; no same-stage check is performed, so it can be used to re-enter the current stage.