@inglorious/store 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -90,7 +90,7 @@ Creates a convenient API object that encapsulates the store's methods and provid
90
90
 
91
91
  - An `api` object with methods for interacting with the store and state, including:
92
92
  - `createSelector(inputSelectors, resultFunc)`: A helper function that automatically binds the store's state to a new selector.
93
- - `getTypes()`, `getEntities()`, `getEntity(id)`, `getType(id)`: Utility functions for accessing state.
93
+ - `getTypes()`, `getEntities()`, `getEntity(id)`: Utility functions for accessing state.
94
94
  - `notify(type, payload)`, `dispatch(action)`: Aliases to the store's event dispatching methods.
95
95
 
96
96
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/store",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "A state manager inspired by Redux, but tailored for the specific needs of game development.",
5
5
  "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
package/src/api.js CHANGED
@@ -12,23 +12,12 @@ export function createApi(store) {
12
12
 
13
13
  const getEntity = (id) => getEntities()[id]
14
14
 
15
- const notify = (type, payload) => {
16
- store.notify(type, payload)
17
- }
18
-
19
- const dispatch = (action) => {
20
- store.dispatch(action)
21
- }
22
-
23
- const getType = (id) => store.getOriginalTypes()?.[id]
24
-
25
15
  return {
26
16
  createSelector,
27
17
  getTypes,
28
18
  getEntities,
29
19
  getEntity,
30
- getType,
31
- notify,
32
- dispatch,
20
+ dispatch: store.dispatch,
21
+ notify: store.notify,
33
22
  }
34
23
  }
package/src/store.js CHANGED
@@ -6,6 +6,7 @@ import { augmentType, augmentTypes } from "./types.js"
6
6
 
7
7
  /**
8
8
  * Creates a store to manage state and events.
9
+ *
9
10
  * @param {Object} config - Configuration options for the store.
10
11
  * @param {Object} [config.types] - The initial types configuration.
11
12
  * @param {Object} [config.entities] - The initial entities configuration.
@@ -17,14 +18,11 @@ export function createStore({
17
18
  systems = [],
18
19
  }) {
19
20
  const listeners = new Set()
20
- let incomingEvents = []
21
21
 
22
22
  const types = augmentTypes(originalTypes)
23
- const eventMap = new EventMap(types, originalEntities)
24
23
 
25
- const entities = augmentEntities(originalEntities)
26
- const initialState = { entities }
27
- let state = initialState
24
+ let state, eventMap, incomingEvents
25
+ reset()
28
26
 
29
27
  return {
30
28
  subscribe,
@@ -40,6 +38,7 @@ export function createStore({
40
38
 
41
39
  /**
42
40
  * Subscribes a listener to state updates.
41
+ *
43
42
  * @param {Function} listener - The listener function to call on updates.
44
43
  * @returns {Function} A function to unsubscribe the listener.
45
44
  */
@@ -53,15 +52,14 @@ export function createStore({
53
52
 
54
53
  /**
55
54
  * Updates the state based on elapsed time and processes events.
55
+ *
56
56
  * @param {number} dt - The delta time since the last update in milliseconds.
57
57
  * @param {Object} api - The engine's public API.
58
58
  */
59
- function update(dt, api) {
59
+ function update(api) {
60
60
  const processedEvents = []
61
61
 
62
62
  state = produce(state, (state) => {
63
- incomingEvents.push({ type: "update", payload: dt })
64
-
65
63
  while (incomingEvents.length) {
66
64
  const event = incomingEvents.shift()
67
65
  processedEvents.push(event)
@@ -86,6 +84,7 @@ export function createStore({
86
84
  const type = types[entity.type]
87
85
 
88
86
  eventMap.addEntity(id, type)
87
+ incomingEvents.unshift({ type: "create", payload: id })
89
88
  }
90
89
 
91
90
  if (event.type === "remove") {
@@ -95,6 +94,7 @@ export function createStore({
95
94
  delete state.entities[id]
96
95
 
97
96
  eventMap.removeEntity(id, type)
97
+ incomingEvents.unshift({ type: "destroy", payload: id })
98
98
  }
99
99
 
100
100
  const entityIds = eventMap.getEntitiesForEvent(event.type)
@@ -102,7 +102,7 @@ export function createStore({
102
102
  const entity = state.entities[id]
103
103
  const type = types[entity.type]
104
104
  const handle = type[event.type]
105
- handle?.(entity, event.payload, api)
105
+ handle(entity, event.payload, api)
106
106
  }
107
107
 
108
108
  systems.forEach((system) => {
@@ -119,6 +119,7 @@ export function createStore({
119
119
 
120
120
  /**
121
121
  * Notifies the store of a new event.
122
+ *
122
123
  * @param {string} type - The event object type to notify.
123
124
  * @param {any} payload - The event object payload to notify.
124
125
  */
@@ -128,6 +129,7 @@ export function createStore({
128
129
 
129
130
  /**
130
131
  * Dispatches an event to be processed in the next update cycle.
132
+ *
131
133
  * @param {Object} event - The event object.
132
134
  * @param {string} event.type - The type of the event.
133
135
  * @param {any} [event.payload] - The payload of the event.
@@ -139,6 +141,7 @@ export function createStore({
139
141
  /**
140
142
  * Retrieves the augmented types configuration.
141
143
  * This includes composed behaviors and event handlers wrapped for immutability.
144
+ *
142
145
  * @returns {Object} The augmented types configuration.
143
146
  */
144
147
  function getTypes() {
@@ -147,6 +150,7 @@ export function createStore({
147
150
 
148
151
  /**
149
152
  * Retrieves the original, un-augmented types configuration.
153
+ *
150
154
  * @returns {Object} The original types configuration.
151
155
  */
152
156
  function getOriginalTypes() {
@@ -155,17 +159,50 @@ export function createStore({
155
159
 
156
160
  /**
157
161
  * Retrieves the current state.
162
+ *
158
163
  * @returns {Object} The current state.
159
164
  */
160
165
  function getState() {
161
166
  return state
162
167
  }
163
168
 
164
- function setState(newState) {
165
- state = newState
169
+ /**
170
+ * Sets the entire state of the store.
171
+ * This is useful for importing state or setting initial state from a server.
172
+ *
173
+ * @param {Object} nextState - The new state to set.
174
+ */
175
+ function setState(nextState) {
176
+ const oldEntities = state?.entities ?? {}
177
+ const newEntities = augmentEntities(nextState.entities)
178
+
179
+ state = { entities: newEntities }
180
+ eventMap = new EventMap(types, nextState.entities)
181
+ incomingEvents = []
182
+
183
+ const oldEntityIds = new Set(Object.keys(oldEntities))
184
+ const newEntityIds = new Set(Object.keys(newEntities))
185
+
186
+ const entitiesToCreate = [...newEntityIds].filter(
187
+ (id) => !oldEntityIds.has(id),
188
+ )
189
+ const entitiesToDestroy = [...oldEntityIds].filter(
190
+ (id) => !newEntityIds.has(id),
191
+ )
192
+
193
+ entitiesToCreate.forEach((id) => {
194
+ incomingEvents.push({ type: "create", payload: id })
195
+ })
196
+
197
+ entitiesToDestroy.forEach((id) => {
198
+ incomingEvents.push({ type: "destroy", payload: id })
199
+ })
166
200
  }
167
201
 
202
+ /**
203
+ * Resets the store to its initial state.
204
+ */
168
205
  function reset() {
169
- state = initialState
206
+ setState({ entities: originalEntities })
170
207
  }
171
208
  }
package/src/store.test.js CHANGED
@@ -3,6 +3,37 @@ import { expect, test } from "vitest"
3
3
  import { createStore } from "./store.js"
4
4
 
5
5
  test("it should process events by mutating state inside handlers", () => {
6
+ const config = {
7
+ types: {
8
+ kitty: {
9
+ feed(entity) {
10
+ entity.isFed = true
11
+ },
12
+ },
13
+ },
14
+ entities: {
15
+ kitty1: { type: "kitty" },
16
+ },
17
+ }
18
+ const afterState = {
19
+ entities: {
20
+ kitty1: {
21
+ id: "kitty1",
22
+ type: "kitty",
23
+ isFed: true,
24
+ },
25
+ },
26
+ }
27
+
28
+ const store = createStore(config)
29
+ store.notify("feed")
30
+ store.update()
31
+
32
+ const state = store.getState()
33
+ expect(state).toStrictEqual(afterState)
34
+ })
35
+
36
+ test("it should process an event queue in the same update cycle", () => {
6
37
  const config = {
7
38
  types: {
8
39
  kitty: {
@@ -31,7 +62,8 @@ test("it should process events by mutating state inside handlers", () => {
31
62
 
32
63
  const store = createStore(config)
33
64
  store.notify("feed")
34
- store.update(0, {})
65
+ store.notify("update")
66
+ store.update()
35
67
 
36
68
  const state = store.getState()
37
69
  expect(state).toStrictEqual(afterState)
@@ -65,7 +97,8 @@ test("it should send an event from an entity and process it in the same update c
65
97
 
66
98
  const store = createStore(config)
67
99
  const api = { notify: store.notify }
68
- store.update(0, api)
100
+ store.notify("update")
101
+ store.update(api)
69
102
 
70
103
  const state = store.getState()
71
104
  expect(state).toStrictEqual(afterState)
@@ -82,7 +115,7 @@ test("it should add an entity via an 'add' event", () => {
82
115
 
83
116
  const store = createStore(config)
84
117
  store.notify("add", newEntity)
85
- store.update(0, {})
118
+ store.update()
86
119
 
87
120
  const state = store.getState()
88
121
  expect(state).toStrictEqual({
@@ -102,8 +135,7 @@ test("it should remove an entity via a 'remove' event", () => {
102
135
  const store = createStore(config)
103
136
 
104
137
  store.notify("remove", "kitty1")
105
-
106
- store.update(0, {})
138
+ store.update()
107
139
 
108
140
  const state = store.getState()
109
141
  expect(state.entities.kitty1).toBeUndefined()
@@ -134,7 +166,8 @@ test("it should change an entity's behavior via a 'morph' event", () => {
134
166
  const store = createStore(config)
135
167
 
136
168
  store.notify("eat")
137
- store.update(0, {})
169
+ store.update()
170
+
138
171
  expect(store.getState()).toStrictEqual({
139
172
  entities: {
140
173
  bug: { id: "bug", type: "bug", isFull: true },
@@ -143,7 +176,7 @@ test("it should change an entity's behavior via a 'morph' event", () => {
143
176
 
144
177
  store.notify("morph", { id: "bug", type: [Caterpillar, Butterfly] })
145
178
  store.notify("fly")
146
- store.update(0, {})
179
+ store.update()
147
180
  expect(store.getState()).toStrictEqual({
148
181
  entities: {
149
182
  bug: { id: "bug", type: "bug", isFull: true, hasFlown: true },