@inglorious/engine 0.11.0 → 1.1.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": "0.11.0",
3
+ "version": "1.1.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,8 +30,8 @@
30
30
  "access": "public"
31
31
  },
32
32
  "dependencies": {
33
- "@inglorious/store": "3.0.0",
34
- "@inglorious/utils": "3.0.0"
33
+ "@inglorious/store": "4.0.0",
34
+ "@inglorious/utils": "3.2.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "prettier": "^3.5.3",
@@ -1,6 +1,5 @@
1
1
  import { audio } from "@inglorious/engine/behaviors/audio.js"
2
2
  import { game } from "@inglorious/engine/behaviors/game.js"
3
- import { createApi } from "@inglorious/store/api.js"
4
3
  import { createStore } from "@inglorious/store/store.js"
5
4
  import { augmentType } from "@inglorious/store/types.js"
6
5
  import { isArray } from "@inglorious/utils/data-structures/array.js"
@@ -10,10 +9,8 @@ import { v } from "@inglorious/utils/v.js"
10
9
 
11
10
  import { coreEvents } from "./core-events.js"
12
11
  import { disconnectDevTools, initDevTools, sendAction } from "./dev-tools.js"
13
- import { Loops } from "./loops/loops.js"
12
+ import { Loop } from "./loops/index.js"
14
13
  import { entityPoolMiddleware } from "./middlewares/entity-pool/entity-pool-middleware.js"
15
- import { EntityPools } from "./middlewares/entity-pool/entity-pools.js"
16
- import { applyMiddlewares } from "./middlewares/middlewares.js"
17
14
  import { multiplayerMiddleware } from "./middlewares/multiplayer-middleware.js"
18
15
 
19
16
  // Default game configuration
@@ -42,44 +39,29 @@ const ONE_SECOND = 1000 // Number of milliseconds in one second.
42
39
  */
43
40
  export class Engine {
44
41
  /**
45
- * @param {Object} [gameConfig] - Game-specific configuration.
46
- * @param {Object} [renderer] - UI entity responsible for rendering. It must have a `render` method.
42
+ * @param {...Object} gameConfigs - Game-specific configurations.
47
43
  */
48
44
  constructor(...gameConfigs) {
49
45
  this._config = extendWith(merger, DEFAULT_GAME_CONFIG, ...gameConfigs)
50
46
 
51
- // Determine devMode from the entities config.
47
+ // Determine devMode from the entities config
52
48
  const devMode = this._config.entities.game?.devMode
53
49
  this._devMode = devMode
54
50
 
55
- // Add user-defined systems
56
- const systems = [...(this._config.systems ?? [])]
51
+ const middlewares = []
57
52
 
58
- this._store = createStore({ ...this._config, systems })
53
+ // Always add entity pool middleware
54
+ middlewares.push(entityPoolMiddleware())
59
55
 
60
- // Create API layer, with optional methods for debugging
61
- this._api = createApi(this._store)
62
-
63
- this._entityPools = new EntityPools()
64
- this._api = applyMiddlewares(entityPoolMiddleware(this._entityPools))(
65
- this._api,
66
- )
67
- this._api.getAllActivePoolEntities = () =>
68
- this._entityPools.getAllActiveEntities()
69
-
70
- if (this._devMode) {
71
- this._api.getEntityPoolsStats = () => this._entityPools.getStats()
72
- }
73
-
74
- // Apply multiplayer if specified.
56
+ // Add multiplayer middleware if needed
75
57
  const multiplayer = this._config.entities.game?.multiplayer
76
58
  if (multiplayer) {
77
- this._api = applyMiddlewares(multiplayerMiddleware(multiplayer))(
78
- this._api,
79
- )
59
+ middlewares.push(multiplayerMiddleware(multiplayer))
80
60
  }
81
61
 
82
- this._loop = new Loops[this._config.loop.type]()
62
+ this._store = createStore({ ...this._config, middlewares })
63
+ this._api = this._store.getApi()
64
+ this._loop = new Loop[this._config.loop.type]()
83
65
 
84
66
  if (this._devMode) {
85
67
  initDevTools(this._store)
@@ -109,7 +91,7 @@ export class Engine {
109
91
  */
110
92
  stop() {
111
93
  this._api.notify("stop")
112
- this._store.update(this._api)
94
+ this._store.update()
113
95
  this._loop.stop()
114
96
  }
115
97
 
@@ -119,7 +101,7 @@ export class Engine {
119
101
  */
120
102
  update(dt) {
121
103
  this._api.notify("update", dt)
122
- const processedEvents = this._store.update(this._api)
104
+ const processedEvents = this._store.update()
123
105
  const state = this._store.getState()
124
106
 
125
107
  // Check for devMode changes and connect/disconnect dev tools accordingly.
@@ -6,7 +6,7 @@ import { LagLoop } from "./lag.js"
6
6
 
7
7
  // @see https://gameprogrammingpatterns.com/game-loop.html
8
8
 
9
- export const Loops = {
9
+ export const Loop = {
10
10
  flash: FlashLoop,
11
11
  fixed: FixedLoop,
12
12
  elapsed: ElapsedLoop,
@@ -1,10 +1,20 @@
1
1
  import { EventMap } from "@inglorious/store/event-map.js"
2
2
 
3
- export function entityPoolMiddleware(pools) {
4
- return (api) => {
3
+ import { EntityPools } from "./entity-pools"
4
+
5
+ export function entityPoolMiddleware() {
6
+ return (store, api) => {
7
+ const pools = new EntityPools()
5
8
  const types = api.getTypes()
6
9
  const eventMap = new EventMap()
7
10
 
11
+ api.getAllActivePoolEntities = () => pools.getAllActiveEntities()
12
+
13
+ const game = api.getEntity("game")
14
+ if (game.devMode) {
15
+ api.getEntityPoolsStats = () => pools.getStats()
16
+ }
17
+
8
18
  return (next) => (event) => {
9
19
  switch (event.type) {
10
20
  case "spawn": {
@@ -1,3 +1,7 @@
1
+ import {
2
+ deserialize,
3
+ serialize,
4
+ } from "@inglorious/utils/data-structures/object.js"
1
5
  import { extend } from "@inglorious/utils/data-structures/objects.js"
2
6
 
3
7
  import { coreEvents } from "../core-events.js"
@@ -19,21 +23,21 @@ export function multiplayerMiddleware(config = {}) {
19
23
  const localQueue = []
20
24
 
21
25
  // The middleware function that will be applied to the store.
22
- return (api) => (next) => (event) => {
26
+ return (store) => (next) => (event) => {
23
27
  if (coreEvents.includes(event.type)) {
24
28
  return next(event)
25
29
  }
26
30
 
27
31
  // Establish the connection on the first event.
28
32
  if (!ws) {
29
- establishConnection(api)
33
+ establishConnection(store)
30
34
  }
31
35
 
32
36
  // Only send the event to the server if it didn't come from the server.
33
37
  if (!event.fromServer) {
34
38
  if (ws?.readyState === WebSocket.OPEN) {
35
39
  // If the connection is open, send the event immediately.
36
- ws.send(JSON.stringify(event))
40
+ ws.send(serialize(event))
37
41
  } else {
38
42
  // If the connection is not open, queue the event for later.
39
43
  localQueue.push(event)
@@ -48,7 +52,7 @@ export function multiplayerMiddleware(config = {}) {
48
52
  /**
49
53
  * Attempts to establish a WebSocket connection to the server.
50
54
  */
51
- function establishConnection(api) {
55
+ function establishConnection(store) {
52
56
  // If a connection already exists, close it first.
53
57
  if (ws) {
54
58
  ws.close()
@@ -62,26 +66,26 @@ export function multiplayerMiddleware(config = {}) {
62
66
  ws.onopen = () => {
63
67
  // Send any queued events to the server.
64
68
  while (localQueue.length) {
65
- ws.send(JSON.stringify(localQueue.shift()))
69
+ ws.send(serialize(localQueue.shift()))
66
70
  }
67
71
  }
68
72
 
69
73
  ws.onmessage = (event) => {
70
- const serverEvent = JSON.parse(event.data)
74
+ const serverEvent = deserialize(event.data)
71
75
 
72
76
  if (serverEvent.type === "initialState") {
73
77
  // Merge the server's initial state with the client's local state.
74
- const nextState = extend(api.getState(), serverEvent.payload)
75
- api.setState(nextState)
78
+ const nextState = extend(store.getState(), serverEvent.payload)
79
+ store.setState(nextState)
76
80
  } else {
77
81
  // Dispatch the event to the local store to update the client's state.
78
- api.dispatch({ ...serverEvent, fromServer: true })
82
+ store.dispatch({ ...serverEvent, fromServer: true })
79
83
  }
80
84
  }
81
85
 
82
86
  ws.onclose = () => {
83
87
  // Attempt to reconnect after a short delay.
84
- setTimeout(() => establishConnection(api), reconnectionDelay)
88
+ setTimeout(() => establishConnection(store), reconnectionDelay)
85
89
  }
86
90
 
87
91
  ws.onerror = () => {
@@ -1,32 +0,0 @@
1
- import { compose } from "@inglorious/utils/functions/functions.js"
2
-
3
- /**
4
- * Applies a list of middleware functions to a store's dispatch method.
5
- *
6
- * @param {...Function} middlewares The middleware functions to apply.
7
- * @returns {Function} A store enhancer function.
8
- */
9
- export function applyMiddlewares(...middlewares) {
10
- return (store) => {
11
- let dispatch = () => {
12
- throw new Error(
13
- "Dispatching while constructing your middleware is not allowed.",
14
- )
15
- }
16
-
17
- // The middleware API that can be passed to each middleware function.
18
- const api = {
19
- ...store,
20
- dispatch: (...args) => dispatch(...args),
21
- notify: (type, payload) => dispatch({ type, payload }),
22
- }
23
-
24
- // Create a chain of middleware functions.
25
- const chain = middlewares.map((middleware) => middleware(api))
26
-
27
- // Compose the middleware chain to create the final dispatch function.
28
- dispatch = compose(...chain)(store.dispatch)
29
-
30
- return api
31
- }
32
- }