@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 +1 -1
- package/package.json +1 -1
- package/src/api.js +2 -13
- package/src/store.js +49 -12
- package/src/store.test.js +40 -7
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)
|
|
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": "
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
179
|
+
store.update()
|
|
147
180
|
expect(store.getState()).toStrictEqual({
|
|
148
181
|
entities: {
|
|
149
182
|
bug: { id: "bug", type: "bug", isFull: true, hasFlown: true },
|