@inglorious/engine 3.0.0 → 4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/engine",
3
- "version": "3.0.0",
3
+ "version": "4.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.0",
34
- "@inglorious/utils": "3.6.0"
33
+ "@inglorious/utils": "3.6.0",
34
+ "@inglorious/store": "5.1.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "@inglorious/store": "5.0.0",
38
- "@inglorious/utils": "3.6.0"
37
+ "@inglorious/utils": "3.6.0",
38
+ "@inglorious/store": "5.1.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "prettier": "^3.5.3",
@@ -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
 
@@ -111,7 +117,7 @@ export class Engine {
111
117
  const newDevMode = state.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
  }
@@ -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
- }