@@ -30,7 +30,8 @@ handles the rest.
3030** What it does:**
3131
3232- Automatically creates and destroys sessions on player join/quit (for lifecycle sessions)
33- - Routes PMMP events to the correct player's session using ` #[SessionEventHandler] ` attributes — no listener classes to write
33+ - Routes PMMP events to the correct player's session using ` #[SessionEventHandler] ` attributes — no listener classes to
34+ write
3435- Supports ordered session progression via ` SessionSequence ` for multi-step flows like tutorials
3536- Prevents duplicate PMMP listener registration across multiple session types via a global registry
3637- Provides a generic, type-safe ` SessionManager<T> ` for clean plugin architecture
@@ -76,11 +77,15 @@ class SessionManager~T extends Session~ {
7677 -array sessions
7778 -list eventBindingList
7879 +getSession(playerOrId) T?
80+ +getSessionOrThrow(playerOrId) T
81+ +getOrCreateSession(player) T?
7982 +createSession(player, ...args) T?
8083 +getAllSessions() list~T~
8184 +removeSession(session, reason) void
8285 +terminateAll(reason) int
8386 +getSessionClass() class-string
87+ +onSessionCreated(callback) self
88+ +onSessionTerminated(callback) self
8489}
8590
8691class SequenceSessionManager {
@@ -112,13 +117,21 @@ class SessionEventListener {
112117 +hasDispatchers() bool
113118}
114119
115- class SessionEventDispatcher {
116- +SessionManager sessionManager
120+ class BaseSessionEventDispatcher {
121+ <<abstract>>
122+ +string eventKey
117123 +string eventClass
118124 +int priority
119125 +bool handleCancelled
120126 +string methodName
121- +string eventKey
127+ +dispatch(event, player) void
128+ }
129+
130+ class SessionMethodDispatcher {
131+ +dispatch(event, player) void
132+ }
133+
134+ class ManagerMethodDispatcher {
122135 +dispatch(event, player) void
123136}
124137
@@ -137,8 +150,10 @@ SequenceSessionManager --> SequenceSession : manages
137150SessionManager --> Session : manages
138151SessionManager --> SessionEventListenerRegistry : registers via
139152SessionEventListenerRegistry --> SessionEventListener : owns one per eventKey
140- SessionEventListener --> SessionEventDispatcher : dispatches to
141- SessionEventDispatcher ..> SessionEventHandler : created from
153+ SessionEventListener --> BaseSessionEventDispatcher : dispatches to
154+ BaseSessionEventDispatcher <|-- SessionMethodDispatcher
155+ BaseSessionEventDispatcher <|-- ManagerMethodDispatcher
156+ SessionMethodDispatcher ..> SessionEventHandler : created from
142157```
143158
144159### Event flow
@@ -147,9 +162,9 @@ SessionEventDispatcher ..> SessionEventHandler : created from
147162PMMP fires event
148163 → SessionEventListener::onEvent()
149164 → [cancelled mid-dispatch? stop if handleCancelled=false]
150- → SessionEventDispatcher ::dispatch()
151- → SessionManager::getSession(player)
152- → Session ::{methodName}(event)
165+ → BaseSessionEventDispatcher ::dispatch()
166+ ├─ SessionMethodDispatcher → SessionManager::getSession(player) → Session::{methodName}(event )
167+ └─ ManagerMethodDispatcher → SessionManager ::{methodName}(event)
153168```
154169
155170---
@@ -197,9 +212,17 @@ All session classes passed to `SessionSequence` must extend `SequenceSession` an
197212
198213Central orchestrator for one session type. On construction:
199214
200- 1 . Scans the session class for ` #[SessionEventHandler] ` attributes
201- 2 . Registers the resulting dispatchers with ` SessionEventListenerRegistry `
202- 3 . Registers join/quit lifecycle listeners (if ` LifecycleSession ` )
215+ 1 . Collects lifecycle dispatchers for ` PlayerJoinEvent ` / ` PlayerQuitEvent ` (if ` LifecycleSession ` )
216+ 2 . Scans the session class for ` #[SessionEventHandler] ` attributes
217+ 3 . Registers all collected dispatchers with ` SessionEventListenerRegistry `
218+
219+ ** Key methods:**
220+
221+ - ` getSession(Player|int) ` — Returns the active session or null.
222+ - ` getSessionOrThrow(Player|int) ` — Returns the active session or throws if none exists.
223+ - ` getOrCreateSession(Player) ` — Returns the active session or creates one if none exists.
224+ - ` onSessionCreated(Closure) ` — Registers a callback invoked after a session is created and started.
225+ - ` onSessionTerminated(Closure) ` — Registers a callback invoked after a session is terminated and removed.
203226
204227### ` #[SessionEventHandler] ` (attribute)
205228
@@ -208,18 +231,24 @@ events. The method must be `public` and accept exactly one non-nullable `Event`
208231
209232### ` SessionEventListenerRegistry ` (singleton)
210233
211- Ensures only one PMMP listener exists per unique ` (eventClass, priority, handleCancelled) ` combination — the ** eventKey** .
212- Multiple session types subscribing to the same event share a single PMMP listener.
234+ Ensures only one PMMP listener exists per unique ` (eventClass, priority, handleCancelled) ` combination — the ** eventKey
235+ ** .
236+ Multiple session types subscribing to the same event share a single PMMP listener. This applies to both
237+ session event handlers and lifecycle events (` PlayerJoinEvent ` , ` PlayerQuitEvent ` ).
213238
214239### ` SessionEventListener ` (class)
215240
216- The actual PMMP-registered listener for one eventKey. Holds a list of ` SessionEventDispatcher ` instances and routes
217- each fired event to the correct player's session. Respects cancellation state mid-dispatch.
241+ The actual PMMP-registered listener for one eventKey. Holds a list of ` BaseSessionEventDispatcher ` instances and routes
242+ each fired event to them in registration order. Respects cancellation state mid-dispatch.
243+
244+ ### ` BaseSessionEventDispatcher ` (abstract class)
218245
219- ### ` SessionEventDispatcher ` (class)
246+ Base class for all event dispatchers. Holds the listener configuration (` eventKey ` , ` eventClass ` , ` priority ` ,
247+ ` handleCancelled ` , ` methodName ` ) and defines the ` dispatch() ` contract. Two concrete subclasses are provided:
220248
221- Represents one ` #[SessionEventHandler] ` binding. Holds its event configuration (` eventKey ` ), the target
222- ` SessionManager ` , and the method name to invoke. Called by ` SessionEventListener ` on each event.
249+ - ` SessionMethodDispatcher ` — Looks up the player's active session and invokes the bound method on it.
250+ - ` ManagerMethodDispatcher ` — Invokes the bound method directly on the ` SessionManager ` instance. Used internally for
251+ lifecycle event handling.
223252
224253### ` SessionTerminateReasons ` (interface)
225254
@@ -240,11 +269,14 @@ src/kim/present/utils/session/
240269├── SessionSequence.php
241270├── SessionTerminateReasons.php
242271└── listener/
243- ├── SessionEventDispatcher.php
244272 ├── SessionEventListener.php
245273 ├── SessionEventListenerRegistry.php
246- └── attribute/
247- └── SessionEventHandler.php
274+ ├── attribute/
275+ │ └── SessionEventHandler.php
276+ └── dispatcher/
277+ ├── BaseSessionEventDispatcher.php
278+ ├── SessionMethodDispatcher.php
279+ └── ManagerMethodDispatcher.php
248280```
249281
250282---
@@ -309,6 +341,14 @@ final class MyPlugin extends PluginBase{
309341
310342 protected function onEnable() : void{
311343 $this->sessionManager = new SessionManager($this, WorldEditSession::class);
344+
345+ $this->sessionManager
346+ ->onSessionCreated(function(WorldEditSession $session) : void{
347+ // e.g. log session start
348+ })
349+ ->onSessionTerminated(function(WorldEditSession $session, string $reason) : void{
350+ // e.g. persist state to DB
351+ });
312352 }
313353
314354 protected function onDisable() : void{
@@ -323,9 +363,15 @@ final class MyPlugin extends PluginBase{
323363// Create a session on demand (for non-lifecycle sessions)
324364$session = $this->sessionManager->createSession($player);
325365
326- // Retrieve a session
366+ // Retrieve a session (returns null if none exists)
327367$session = $this->sessionManager->getSession($player);
328368
369+ // Retrieve a session or throw if none exists
370+ $session = $this->sessionManager->getSessionOrThrow($player);
371+
372+ // Retrieve a session or create one if none exists
373+ $session = $this->sessionManager->getOrCreateSession($player);
374+
329375// Remove a specific session
330376$this->sessionManager->removeSession($player, SessionTerminateReasons::MANUAL);
331377
@@ -458,9 +504,15 @@ Distributed under the **MIT License**. See [LICENSE][license-url] for more infor
458504---
459505
460506[ poggit-ci-badge ] : https://poggit.pmmp.io/ci.shield/presentkim-pm/session-utils/session-utils?style=for-the-badge
507+
461508[ stars-badge ] : https://img.shields.io/github/stars/presentkim-pm/session-utils.svg?style=for-the-badge
509+
462510[ license-badge ] : https://img.shields.io/github/license/presentkim-pm/session-utils.svg?style=for-the-badge
511+
463512[ poggit-ci-url ] : https://poggit.pmmp.io/ci/presentkim-pm/session-utils/session-utils
513+
464514[ stars-url ] : https://github.com/presentkim-pm/session-utils/stargazers
515+
465516[ issues-url ] : https://github.com/presentkim-pm/session-utils/issues
517+
466518[ license-url ] : https://github.com/presentkim-pm/session-utils/blob/main/LICENSE
0 commit comments