@inglorious/engine 3.0.1 → 5.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
@@ -100,6 +100,14 @@ Here is a complete example showing how to set up and run a game using the engine
100
100
 
101
101
  ---
102
102
 
103
+ ## License
104
+
105
+ MIT © [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
106
+
107
+ This is free and open-source software. Use it however you want!
108
+
109
+ ---
110
+
103
111
  ## Contributing
104
112
 
105
113
  We welcome contributions from the community\! Whether you're fixing a bug, adding a feature, or improving the documentation, your help is appreciated. Please read our [Contributing Guidelines](https://github.com/IngloriousCoderz/inglorious-engine/blob/main/CONTRIBUTING.md) for details on how to get started.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/engine",
3
- "version": "3.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "A JavaScript game engine written with global state, immutability, and pure functions in mind. Have fun(ctional programming) with it!",
5
5
  "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -30,12 +30,12 @@
30
30
  "access": "public"
31
31
  },
32
32
  "dependencies": {
33
- "@inglorious/store": "5.0.1",
34
- "@inglorious/utils": "3.6.0"
33
+ "@inglorious/store": "5.2.0",
34
+ "@inglorious/utils": "3.6.1"
35
35
  },
36
36
  "peerDependencies": {
37
- "@inglorious/store": "5.0.1",
38
- "@inglorious/utils": "3.6.0"
37
+ "@inglorious/store": "5.2.0",
38
+ "@inglorious/utils": "3.6.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "prettier": "^3.5.3",
@@ -29,13 +29,11 @@ test("it should add a finite state machine", () => {
29
29
  },
30
30
  }
31
31
  const afterState = {
32
- entities: {
33
- entity1: {
34
- id: "entity1",
35
- type: "kitty",
36
- state: "meowing",
37
- treats: 1,
38
- },
32
+ entity1: {
33
+ id: "entity1",
34
+ type: "kitty",
35
+ state: "meowing",
36
+ treats: 1,
39
37
  },
40
38
  }
41
39
 
@@ -1,6 +1,12 @@
1
1
  import { audio } from "@inglorious/engine/behaviors/audio.js"
2
2
  import { game } from "@inglorious/engine/behaviors/game.js"
3
3
  import { images } from "@inglorious/engine/behaviors/images.js"
4
+ import {
5
+ connectDevTools,
6
+ disconnectDevTools,
7
+ sendAction,
8
+ } from "@inglorious/store/client/dev-tools.js"
9
+ import { multiplayerMiddleware } from "@inglorious/store/client/multiplayer-middleware.js"
4
10
  import { createStore } from "@inglorious/store/store.js"
5
11
  import { augmentType } from "@inglorious/store/types.js"
6
12
  import { isArray } from "@inglorious/utils/data-structures/array.js"
@@ -9,10 +15,8 @@ import { isVector } from "@inglorious/utils/math/vector.js"
9
15
  import { v } from "@inglorious/utils/v.js"
10
16
 
11
17
  import { coreEvents } from "./core-events.js"
12
- import { disconnectDevTools, initDevTools, sendAction } from "./dev-tools.js"
13
18
  import { Loop } from "./loops/index.js"
14
19
  import { entityPoolMiddleware } from "./middlewares/entity-pool/entity-pool-middleware.js"
15
- import { multiplayerMiddleware } from "./middlewares/multiplayer-middleware.js"
16
20
 
17
21
  // Default game configuration
18
22
  // loop.type specifies the type of loop to use (defaults to "animationFrame").
@@ -59,7 +63,9 @@ export class Engine {
59
63
  // Add multiplayer middleware if needed
60
64
  const multiplayer = this._config.entities.game?.multiplayer
61
65
  if (multiplayer) {
62
- middlewares.push(multiplayerMiddleware(multiplayer))
66
+ middlewares.push(
67
+ multiplayerMiddleware({ ...multiplayer, skippedEvents: coreEvents }),
68
+ )
63
69
  }
64
70
 
65
71
  this._store = createStore({ ...this._config, middlewares })
@@ -67,7 +73,7 @@ export class Engine {
67
73
  this._loop = new Loop[this._config.loop.type]()
68
74
 
69
75
  if (this._devMode) {
70
- initDevTools(this._store)
76
+ connectDevTools(this._store, { skippedEvents: coreEvents })
71
77
  }
72
78
  }
73
79
 
@@ -105,13 +111,13 @@ export class Engine {
105
111
  update(dt) {
106
112
  this._api.notify("update", dt)
107
113
  const processedEvents = this._store.update()
108
- const state = this._store.getState()
114
+ const entities = this._store.getState()
109
115
 
110
116
  // Check for devMode changes and connect/disconnect dev tools accordingly.
111
- const newDevMode = state.entities.game?.devMode
117
+ const newDevMode = entities.game?.devMode
112
118
  if (newDevMode !== this._devMode) {
113
119
  if (newDevMode) {
114
- initDevTools(this._store)
120
+ connectDevTools(this._store, { skippedEvents: coreEvents })
115
121
  } else {
116
122
  disconnectDevTools()
117
123
  }
@@ -127,7 +133,7 @@ export class Engine {
127
133
  type: eventsToLog.map(({ type }) => type).join("|"),
128
134
  payload: eventsToLog,
129
135
  }
130
- sendAction(action, state)
136
+ sendAction(action, entities)
131
137
  }
132
138
  }
133
139
  }
@@ -5,16 +5,16 @@ import { Sprite } from "@inglorious/engine/animation/sprite.js"
5
5
  * with an active animation state.
6
6
  *
7
7
  * @param {Object} state - The game state, containing all entities.
8
- * @param {Object<string, Object>} state.entities - A map of entity IDs to entity objects.
8
+ * @param {Object<string, Object>} entities - A map of entity IDs to entity objects.
9
9
  * @param {number} dt - The delta time since the last update.
10
10
  * @param {Object} api - The game API.
11
11
  * @param {Function} api.notify - A function to notify about events.
12
12
  */
13
13
  export function spriteAnimationSystem() {
14
14
  return {
15
- update(state, dt, api) {
16
- for (const id in state.entities) {
17
- const entity = state.entities[id]
15
+ update(entities, dt, api) {
16
+ for (const id in entities) {
17
+ const entity = entities[id]
18
18
 
19
19
  if (!entity.sprite || !entity.sprite.state) {
20
20
  continue
@@ -1,122 +0,0 @@
1
- import { coreEvents } from "./core-events.js"
2
-
3
- const LAST_STATE = 1
4
-
5
- let devToolsInstance = null
6
- let unsubscribe = null
7
-
8
- export function initDevTools(store) {
9
- // Prevent multiple connections
10
- if (devToolsInstance) {
11
- return
12
- }
13
-
14
- if (typeof window === "undefined" || !window.__REDUX_DEVTOOLS_EXTENSION__) {
15
- return
16
- }
17
-
18
- devToolsInstance = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
19
- name: "Inglorious Engine",
20
- predicate: (state, action) => !coreEvents.includes(action.type),
21
- actionCreators: {
22
- jump: () => ({ type: "jump", payload: { inputId: "input0" } }),
23
- },
24
- // @see https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#features
25
- features: {
26
- pause: true, // start/pause recording of dispatched actions
27
- lock: true, // lock/unlock dispatching actions and side effects
28
- persist: true, // persist states on page reloading
29
- export: true, // export history of actions in a file
30
- import: "custom", // import history of actions from a file
31
- jump: false, // jump back and forth (time travelling)
32
- skip: false, // skip (cancel) actions
33
- reorder: false, // drag and drop actions in the history list
34
- dispatch: true, // dispatch custom actions or action creators
35
- test: false, // generate tests for the selected actions
36
- },
37
- })
38
-
39
- unsubscribe = devToolsInstance.subscribe((message) => {
40
- switch (message.type) {
41
- case "DISPATCH":
42
- handleDispatch(message, store)
43
- break
44
-
45
- case "ACTION":
46
- handleAction(message, store)
47
- break
48
- }
49
- })
50
-
51
- devToolsInstance.init(store.getState())
52
- }
53
-
54
- export function disconnectDevTools() {
55
- // The `disconnect` method on the devToolsInstance is not available in all
56
- // environments or versions of the extension.
57
- // The safest way to "disconnect" is to unsubscribe from any listeners
58
- // and release our reference to the instance, which prevents any further
59
- // actions from being sent.
60
- if (unsubscribe) {
61
- unsubscribe()
62
- unsubscribe = null
63
- devToolsInstance = null
64
- }
65
- }
66
-
67
- export function sendAction(action, state) {
68
- if (devToolsInstance) {
69
- devToolsInstance.send(action, state)
70
- }
71
- }
72
-
73
- function handleDispatch(message, store) {
74
- switch (message.payload.type) {
75
- // reset button
76
- case "RESET": {
77
- store.reset()
78
- devToolsInstance.init(store.getState())
79
- break
80
- }
81
-
82
- // revert button
83
- case "ROLLBACK": {
84
- const newState = JSON.parse(message.state)
85
- store.setState(newState)
86
- break
87
- }
88
-
89
- // commit button
90
- case "COMMIT": {
91
- devToolsInstance.init(store.getState())
92
- break
93
- }
94
-
95
- // import from file button
96
- case "IMPORT_STATE": {
97
- const { computedStates, actionsById } = message.payload.nextLiftedState
98
-
99
- const [firstComputedState] = computedStates
100
- const lastComputedState =
101
- computedStates[computedStates.length - LAST_STATE]
102
- if (lastComputedState) {
103
- store.setState(lastComputedState.state)
104
- }
105
-
106
- const flattenedActions = Object.values(actionsById)
107
- .flatMap(({ action }) => action.payload ?? action)
108
- .map((action, index) => [index, action])
109
-
110
- devToolsInstance.init(
111
- firstComputedState.state,
112
- Object.fromEntries(flattenedActions),
113
- )
114
- break
115
- }
116
- }
117
- }
118
-
119
- function handleAction(message, store) {
120
- const action = JSON.parse(message.payload)
121
- store.dispatch(action)
122
- }
@@ -1,95 +0,0 @@
1
- import {
2
- deserialize,
3
- serialize,
4
- } from "@inglorious/utils/data-structures/object.js"
5
- import { extend } from "@inglorious/utils/data-structures/objects.js"
6
-
7
- import { coreEvents } from "../core-events.js"
8
-
9
- // A constant for the server's WebSocket URL.
10
- const DEFAULT_SERVER_URL = `ws://${window.location.hostname}:3000`
11
- const DEFAULT_RECONNECTION_DELAY = 1000
12
-
13
- /**
14
- * Creates and returns the multiplayer middleware.
15
- * @returns {Function} The middleware function.
16
- */
17
- export function multiplayerMiddleware(config = {}) {
18
- const serverUrl = config.serverUrl ?? DEFAULT_SERVER_URL
19
- const reconnectionDelay =
20
- config.reconnectionDelay ?? DEFAULT_RECONNECTION_DELAY
21
-
22
- let ws = null
23
- const localQueue = []
24
-
25
- // The middleware function that will be applied to the store.
26
- return (store) => (next) => (event) => {
27
- if (coreEvents.includes(event.type)) {
28
- return next(event)
29
- }
30
-
31
- // Establish the connection on the first event.
32
- if (!ws) {
33
- establishConnection(store)
34
- }
35
-
36
- // Only send the event to the server if it didn't come from the server.
37
- if (!event.fromServer) {
38
- if (ws?.readyState === WebSocket.OPEN) {
39
- // If the connection is open, send the event immediately.
40
- ws.send(serialize(event))
41
- } else {
42
- // If the connection is not open, queue the event for later.
43
- localQueue.push(event)
44
- }
45
- }
46
-
47
- // Pass the event to the next middleware in the chain,
48
- // which is eventually the store's original dispatch function.
49
- return next(event)
50
- }
51
-
52
- /**
53
- * Attempts to establish a WebSocket connection to the server.
54
- */
55
- function establishConnection(store) {
56
- // If a connection already exists, close it first.
57
- if (ws) {
58
- ws.close()
59
- }
60
- ws = new WebSocket(serverUrl)
61
-
62
- // =====================================================================
63
- // WebSocket Event Handlers
64
- // =====================================================================
65
-
66
- ws.onopen = () => {
67
- // Send any queued events to the server.
68
- while (localQueue.length) {
69
- ws.send(serialize(localQueue.shift()))
70
- }
71
- }
72
-
73
- ws.onmessage = (event) => {
74
- const serverEvent = deserialize(event.data)
75
-
76
- if (serverEvent.type === "initialState") {
77
- // Merge the server's initial state with the client's local state.
78
- const nextState = extend(store.getState(), serverEvent.payload)
79
- store.setState(nextState)
80
- } else {
81
- // Dispatch the event to the local store to update the client's state.
82
- store.dispatch({ ...serverEvent, fromServer: true })
83
- }
84
- }
85
-
86
- ws.onclose = () => {
87
- // Attempt to reconnect after a short delay.
88
- setTimeout(() => establishConnection(store), reconnectionDelay)
89
- }
90
-
91
- ws.onerror = () => {
92
- ws.close() // The 'onclose' handler will trigger the reconnect.
93
- }
94
- }
95
- }