game-data-gen 4.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
@@ -1,20 +1,20 @@
1
1
  # Game Data Generation
2
2
 
3
- A Javascript (Typescript) library to generate data structures with zeroing functions.
3
+ A CLI code generator that creates Javascript (Typescript) data structures with zeroing functions.
4
4
 
5
5
  ## The problems
6
6
 
7
- If you're making a game in Javascript then you might (this was actually me):
7
+ If you're making a game in Javascript then you might:
8
8
 
9
9
  - hit the garbage collector (GC) a bunch causing frame drops because you're creating/destroying objects every frame (particles, for example)
10
10
  - read a book about [Data Oriented Design](https://www.amazon.com/dp/1916478700)
11
11
  - notice the performance implications of OOP (especially classes and calling their methods) versus using something like Structure of Arrays
12
- - wanting to implement Structure of Arrays instead of Array of Structures (which is a list of class instances, see previous point)
12
+ - want to implement Structure of Arrays instead of Array of Structures (which is a list of class instances, see previous point)
13
13
  - notice that Javascript can not simply zero out data structures (resetting all data back to initial values) like languages such as C and Rust
14
14
 
15
15
  ## The solution
16
16
 
17
- This library:
17
+ This tool:
18
18
 
19
19
  - creates data structures based on Markdown file (see example below)
20
20
  - each data structure gets associated functions to zero out its memory so it can be reused
@@ -35,19 +35,19 @@ npx game-data-gen <input-file-path> <optional-output-file-path>
35
35
 
36
36
  ### group
37
37
 
38
- | Field type | Example |
39
- | --------------- | ------------------------------ |
40
- | `number` | `- health number` |
41
- | `array int8` | `- ids array int8 64` |
42
- | `array int16` | `- ids array int16 64` |
43
- | `array int32` | `- ids array int32 64` |
44
- | `array uint8` | `- ids array uint8 64` |
45
- | `array uint16` | `- ids array uint16 64` |
46
- | `array uint32` | `- ids array uint32 64` |
47
- | `array float32` | `- positions array float32 64` |
48
- | `array float64` | `- positions array float64 64` |
38
+ | Field type | Example |
39
+ | ------------------ | ------------------------ |
40
+ | (none) | `- health` |
41
+ | `int8 <length>` | `- ids int8 64` |
42
+ | `int16 <length>` | `- ids int16 64` |
43
+ | `int32 <length>` | `- ids int32 64` |
44
+ | `uint8 <length>` | `- ids uint8 64` |
45
+ | `uint16 <length>` | `- ids uint16 64` |
46
+ | `uint32 <length>` | `- ids uint32 64` |
47
+ | `float32 <length>` | `- positions float32 64` |
48
+ | `float64 <length>` | `- positions float64 64` |
49
49
 
50
- Array fields always require a length. A `count` variable and `push`/`pop` functions are generated to simulate a dynamic array within the fixed capacity.
50
+ Whether a field is a scalar or an array is derived from whether a length is provided. A scalar field generates a plain `number`. An array field generates a typed array with a `count` variable and `push`/`pop` functions to simulate a dynamic array within the fixed capacity.
51
51
 
52
52
  ### soa (Structure of Arrays)
53
53
 
@@ -69,16 +69,17 @@ Create a Markdown file somewhere in your source code.
69
69
  For example `src/data.md`:
70
70
 
71
71
  ```md
72
- # game group
72
+ # game
73
73
 
74
- - playerId number
75
- - entityIds array uint16 64
74
+ - playerId
75
+ - enemies uint16 64
76
76
 
77
- # particle soa 10_000
77
+ # entity 1000
78
78
 
79
- - active uint8
80
- - x float32
81
- - y float32
79
+ - type uint8
80
+ - posX float32
81
+ - posY float32
82
+ - health uint16
82
83
  ```
83
84
 
84
85
  Run the package with (consider making this a script in your package.json):
@@ -97,22 +98,22 @@ This will create or update the `src/data.ts` file (see below). The data and func
97
98
  */
98
99
 
99
100
  export let playerId = 0;
100
- export const entityIds = new Uint16Array(64);
101
- export let entityIdsCount = 0;
101
+ export const enemies = new Uint16Array(64);
102
+ export let enemiesCount = 0;
102
103
 
103
104
  /** Set the value of the playerId field within the game group. */
104
105
  export function setPlayerId(v: number) {
105
106
  playerId = v;
106
107
  }
107
108
 
108
- /** Push a value onto the entityIds field within the game group. */
109
- export function pushEntityIds(v: number) {
110
- entityIds[entityIdsCount++] = v;
109
+ /** Push a value onto the enemies field within the game group. */
110
+ export function pushEnemies(v: number) {
111
+ enemies[enemiesCount++] = v;
111
112
  }
112
113
 
113
- /** Pop a value from the entityIds field within the game group. */
114
- export function popEntityIds() {
115
- return entityIds[--entityIdsCount];
114
+ /** Pop a value from the enemies field within the game group. */
115
+ export function popEnemies() {
116
+ return enemies[--enemiesCount];
116
117
  }
117
118
 
118
119
  /** Zero the playerId field within the game group. */
@@ -120,55 +121,54 @@ export function zeroPlayerId() {
120
121
  playerId = 0;
121
122
  }
122
123
 
123
- /** Zero the entityIds field within the game group. */
124
- export function zeroEntityIds() {
125
- entityIdsCount = 0;
124
+ /** Zero the enemies field within the game group. */
125
+ export function zeroEnemies() {
126
+ enemiesCount = 0;
126
127
  }
127
128
 
128
129
  /** Zero all fields within the game group. */
129
130
  export function zeroGame() {
130
131
  playerId = 0;
131
- entityIdsCount = 0;
132
+ enemiesCount = 0;
132
133
  }
133
134
 
134
135
  /*
135
136
  * --------------------------------------------------
136
- * particle (Structure Of Arrays)
137
+ * entity (Structure Of Arrays)
137
138
  * --------------------------------------------------
138
139
  */
139
140
 
140
- export const MAX_PARTICLE_COUNT = 10_000;
141
+ export const MAX_ENTITY_COUNT = 1000;
141
142
 
142
- export const active = new Uint8Array(10_000);
143
- export const x = new Float32Array(10_000);
144
- export const y = new Float32Array(10_000);
143
+ export const type = new Uint8Array(1000);
144
+ export const posX = new Float32Array(1000);
145
+ export const posY = new Float32Array(1000);
146
+ export const health = new Uint16Array(1000);
145
147
 
146
- /** Zero an index within the particle structure of arrays. */
147
- export function zeroParticleAt(i: number) {
148
- active[i] = 0;
149
- x[i] = 0;
150
- y[i] = 0;
148
+ /** Zero an index within the entity structure of arrays. */
149
+ export function zeroEntityAt(i: number) {
150
+ type[i] = 0;
151
+ posX[i] = 0;
152
+ posY[i] = 0;
153
+ health[i] = 0;
151
154
  }
152
155
 
153
- /** Zero the active field within the particle structure of arrays. */
154
- export function zeroActive() {
155
- active.fill(0);
156
+ /** Zero all fields within the entity structure of arrays. */
157
+ export function zeroEntity() {
158
+ type.fill(0);
159
+ posX.fill(0);
160
+ posY.fill(0);
161
+ health.fill(0);
156
162
  }
163
+ ```
157
164
 
158
- /** Zero the x field within the particle structure of arrays. */
159
- export function zeroX() {
160
- x.fill(0);
161
- }
165
+ The `enemies` array in the group acts as a dynamic list within its fixed capacity. Iterate only the active entries using the generated `count`:
162
166
 
163
- /** Zero the y field within the particle structure of arrays. */
164
- export function zeroY() {
165
- y.fill(0);
166
- }
167
+ ```typescript
168
+ import { enemies, enemiesCount } from "./data.ts";
167
169
 
168
- /** Zero all fields within the particle structure of arrays. */
169
- export function zeroParticle() {
170
- active.fill(0);
171
- x.fill(0);
172
- y.fill(0);
170
+ for (let i = 0; i < enemiesCount; i++) {
171
+ const id = enemies[i];
172
+ // ...
173
173
  }
174
174
  ```
package/dist/consts.js CHANGED
@@ -1,21 +1,11 @@
1
- export var Type;
2
- (function (Type) {
3
- Type["SOA"] = "soa";
4
- Type["GROUP"] = "group";
5
- })(Type || (Type = {}));
6
1
  export var FieldType;
7
2
  (function (FieldType) {
8
- FieldType["NUMBER"] = "number";
9
- FieldType["ARRAY"] = "array";
3
+ FieldType["INT_8"] = "int8";
4
+ FieldType["INT_16"] = "int16";
5
+ FieldType["INT_32"] = "int32";
6
+ FieldType["UINT_8"] = "uint8";
7
+ FieldType["UINT_16"] = "uint16";
8
+ FieldType["UINT_32"] = "uint32";
9
+ FieldType["FLOAT_32"] = "float32";
10
+ FieldType["FLOAT_64"] = "float64";
10
11
  })(FieldType || (FieldType = {}));
11
- export var ArrayType;
12
- (function (ArrayType) {
13
- ArrayType["INT_8"] = "int8";
14
- ArrayType["INT_16"] = "int16";
15
- ArrayType["INT_32"] = "int32";
16
- ArrayType["UINT_8"] = "uint8";
17
- ArrayType["UINT_16"] = "uint16";
18
- ArrayType["UINT_32"] = "uint32";
19
- ArrayType["FLOAT_32"] = "float32";
20
- ArrayType["FLOAT_64"] = "float64";
21
- })(ArrayType || (ArrayType = {}));
package/dist/lib/group.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ArrayType, FieldType } from "../consts.js";
1
+ import { FieldType } from "../consts.js";
2
2
  import { addHeader, capitalize } from "./utils.js";
3
3
  export function addGroup(header, fields, output) {
4
4
  const [name] = header.split(" ");
@@ -21,45 +21,47 @@ export function addGroup(header, fields, output) {
21
21
  addZeroFunction(name, fields, output);
22
22
  }
23
23
  function addFieldDefinition(field, output) {
24
- const [fieldName, fieldType, fieldArrayType, fieldArrayLength] = field.split(" ");
24
+ const [fieldName, fieldType, fieldLength] = field.split(" ");
25
25
  switch (fieldType) {
26
- case FieldType.NUMBER:
27
- output.push(`export let ${fieldName} = 0`);
26
+ case FieldType.INT_8:
27
+ output.push(`export const ${fieldName} = new Int8Array(${fieldLength})`);
28
+ output.push(`export let ${fieldName}Count = 0`);
29
+ break;
30
+ case FieldType.INT_16:
31
+ output.push(`export const ${fieldName} = new Int16Array(${fieldLength})`);
32
+ output.push(`export let ${fieldName}Count = 0`);
33
+ break;
34
+ case FieldType.INT_32:
35
+ output.push(`export const ${fieldName} = new Int32Array(${fieldLength})`);
36
+ output.push(`export let ${fieldName}Count = 0`);
37
+ break;
38
+ case FieldType.UINT_8:
39
+ output.push(`export const ${fieldName} = new Uint8Array(${fieldLength})`);
40
+ output.push(`export let ${fieldName}Count = 0`);
41
+ break;
42
+ case FieldType.UINT_16:
43
+ output.push(`export const ${fieldName} = new Uint16Array(${fieldLength})`);
44
+ output.push(`export let ${fieldName}Count = 0`);
45
+ break;
46
+ case FieldType.UINT_32:
47
+ output.push(`export const ${fieldName} = new Uint32Array(${fieldLength})`);
48
+ output.push(`export let ${fieldName}Count = 0`);
49
+ break;
50
+ case FieldType.FLOAT_32:
51
+ output.push(`export const ${fieldName} = new Float32Array(${fieldLength})`);
52
+ output.push(`export let ${fieldName}Count = 0`);
28
53
  break;
29
- case FieldType.ARRAY:
30
- switch (fieldArrayType) {
31
- case ArrayType.INT_8:
32
- output.push(`export const ${fieldName} = new Int8Array(${fieldArrayLength})`);
33
- break;
34
- case ArrayType.INT_16:
35
- output.push(`export const ${fieldName} = new Int16Array(${fieldArrayLength})`);
36
- break;
37
- case ArrayType.INT_32:
38
- output.push(`export const ${fieldName} = new Int32Array(${fieldArrayLength})`);
39
- break;
40
- case ArrayType.UINT_8:
41
- output.push(`export const ${fieldName} = new Uint8Array(${fieldArrayLength})`);
42
- break;
43
- case ArrayType.UINT_16:
44
- output.push(`export const ${fieldName} = new Uint16Array(${fieldArrayLength})`);
45
- break;
46
- case ArrayType.UINT_32:
47
- output.push(`export const ${fieldName} = new Uint32Array(${fieldArrayLength})`);
48
- break;
49
- case ArrayType.FLOAT_32:
50
- output.push(`export const ${fieldName} = new Float32Array(${fieldArrayLength})`);
51
- break;
52
- case ArrayType.FLOAT_64:
53
- output.push(`export const ${fieldName} = new Float64Array(${fieldArrayLength})`);
54
- break;
55
- }
54
+ case FieldType.FLOAT_64:
55
+ output.push(`export const ${fieldName} = new Float64Array(${fieldLength})`);
56
56
  output.push(`export let ${fieldName}Count = 0`);
57
57
  break;
58
+ default:
59
+ output.push(`export let ${fieldName} = 0`);
58
60
  }
59
61
  }
60
62
  function addFieldSetFunction(name, field, output) {
61
- const [fieldName, fieldType] = field.split(" ");
62
- if (fieldType === FieldType.NUMBER) {
63
+ const [fieldName, , fieldLength] = field.split(" ");
64
+ if (!fieldLength) {
63
65
  output.push("");
64
66
  output.push(`/** Set the value of the ${fieldName} field within the ${name} group. */`);
65
67
  output.push(`export function set${capitalize(fieldName)}(v: number) {`);
@@ -68,8 +70,8 @@ function addFieldSetFunction(name, field, output) {
68
70
  }
69
71
  }
70
72
  function addFieldPushFunction(name, field, output) {
71
- const [fieldName, fieldType] = field.split(" ");
72
- if (fieldType === FieldType.ARRAY) {
73
+ const [fieldName, , fieldLength] = field.split(" ");
74
+ if (fieldLength) {
73
75
  output.push("");
74
76
  output.push(`/** Push a value onto the ${fieldName} field within the ${name} group. */`);
75
77
  output.push(`export function push${capitalize(fieldName)}(v: number) {`);
@@ -78,8 +80,8 @@ function addFieldPushFunction(name, field, output) {
78
80
  }
79
81
  }
80
82
  function addFieldPopFunction(name, field, output) {
81
- const [fieldName, fieldType] = field.split(" ");
82
- if (fieldType === FieldType.ARRAY) {
83
+ const [fieldName, , fieldLength] = field.split(" ");
84
+ if (fieldLength) {
83
85
  output.push("");
84
86
  output.push(`/** Pop a value from the ${fieldName} field within the ${name} group. */`);
85
87
  output.push(`export function pop${capitalize(fieldName)}() {`);
@@ -88,11 +90,11 @@ function addFieldPopFunction(name, field, output) {
88
90
  }
89
91
  }
90
92
  function addFieldZeroFunction(name, field, output) {
91
- const [fieldName, fieldType] = field.split(" ");
93
+ const [fieldName, , fieldLength] = field.split(" ");
92
94
  output.push("");
93
95
  output.push(`/** Zero the ${fieldName} field within the ${name} group. */`);
94
96
  output.push(`export function zero${capitalize(fieldName)}() {`);
95
- zeroField(fieldName, fieldType, output);
97
+ zeroField(fieldName, !!fieldLength, output);
96
98
  output.push("}");
97
99
  }
98
100
  function addZeroFunction(name, fields, output) {
@@ -100,18 +102,16 @@ function addZeroFunction(name, fields, output) {
100
102
  output.push(`/** Zero all fields within the ${name} group. */`);
101
103
  output.push(`export function zero${capitalize(name)}() {`);
102
104
  for (const field of fields) {
103
- const [fieldName, fieldType] = field.split(" ");
104
- zeroField(fieldName, fieldType, output);
105
+ const [fieldName, , fieldLength] = field.split(" ");
106
+ zeroField(fieldName, !!fieldLength, output);
105
107
  }
106
108
  output.push("}");
107
109
  }
108
- function zeroField(name, type, output) {
109
- switch (type) {
110
- case FieldType.NUMBER:
111
- output.push(` ${name} = 0`);
112
- break;
113
- case FieldType.ARRAY:
114
- output.push(` ${name}Count = 0`);
115
- break;
110
+ function zeroField(name, isArray, output) {
111
+ if (isArray) {
112
+ output.push(` ${name}Count = 0`);
113
+ }
114
+ else {
115
+ output.push(` ${name} = 0`);
116
116
  }
117
117
  }
package/dist/lib/soa.js CHANGED
@@ -1,17 +1,15 @@
1
- import { ArrayType } from "../consts.js";
1
+ import { FieldType } from "../consts.js";
2
2
  import { addHeader, capitalize } from "./utils.js";
3
3
  export function addStructureOfArrays(header, fields, output) {
4
- const [name, , length] = header.split(" ");
4
+ const [name, length] = header.split(" ");
5
5
  addHeader(`${name} (Structure Of Arrays)`, output);
6
6
  addFieldMaxLengthConstant(name, length, output);
7
7
  for (const field of fields) {
8
8
  addFieldDefinition(field, length, output);
9
9
  }
10
- addFieldZeroAtIndexFunction(name, fields, output);
11
- for (const field of fields) {
12
- addFieldZeroFunction(name, field, output);
13
- }
10
+ addZeroAtIndexFunction(name, fields, output);
14
11
  addZeroFunction(name, fields, output);
12
+ addPrintAtIndexFunction(name, fields, output);
15
13
  }
16
14
  function addFieldMaxLengthConstant(name, length, output) {
17
15
  output.push(`export const MAX_${name.toUpperCase()}_COUNT = ${length}`);
@@ -20,40 +18,32 @@ function addFieldMaxLengthConstant(name, length, output) {
20
18
  function addFieldDefinition(field, length, output) {
21
19
  const [fieldName, fieldType] = field.split(" ");
22
20
  switch (fieldType) {
23
- case ArrayType.INT_8:
21
+ case FieldType.INT_8:
24
22
  output.push(`export const ${fieldName} = new Int8Array(${length})`);
25
23
  break;
26
- case ArrayType.INT_16:
24
+ case FieldType.INT_16:
27
25
  output.push(`export const ${fieldName} = new Int16Array(${length})`);
28
26
  break;
29
- case ArrayType.INT_32:
27
+ case FieldType.INT_32:
30
28
  output.push(`export const ${fieldName} = new Int32Array(${length})`);
31
29
  break;
32
- case ArrayType.UINT_8:
30
+ case FieldType.UINT_8:
33
31
  output.push(`export const ${fieldName} = new Uint8Array(${length})`);
34
32
  break;
35
- case ArrayType.UINT_16:
33
+ case FieldType.UINT_16:
36
34
  output.push(`export const ${fieldName} = new Uint16Array(${length})`);
37
35
  break;
38
- case ArrayType.UINT_32:
36
+ case FieldType.UINT_32:
39
37
  output.push(`export const ${fieldName} = new Uint32Array(${length})`);
40
38
  break;
41
- case ArrayType.FLOAT_32:
39
+ case FieldType.FLOAT_32:
42
40
  output.push(`export const ${fieldName} = new Float32Array(${length})`);
43
41
  break;
44
- case ArrayType.FLOAT_64:
42
+ case FieldType.FLOAT_64:
45
43
  output.push(`export const ${fieldName} = new Float64Array(${length})`);
46
44
  break;
47
45
  }
48
46
  }
49
- function addFieldZeroFunction(name, field, output) {
50
- const [fieldName] = field.split(" ");
51
- output.push("");
52
- output.push(`/** Zero the ${fieldName} field within the ${name} structure of arrays. */`);
53
- output.push(`export function zero${capitalize(fieldName)}() {`);
54
- output.push(` ${fieldName}.fill(0)`);
55
- output.push("}");
56
- }
57
47
  function addZeroFunction(name, fields, output) {
58
48
  output.push("");
59
49
  output.push(`/** Zero all fields within the ${name} structure of arrays. */`);
@@ -64,7 +54,7 @@ function addZeroFunction(name, fields, output) {
64
54
  }
65
55
  output.push("}");
66
56
  }
67
- function addFieldZeroAtIndexFunction(name, fields, output) {
57
+ function addZeroAtIndexFunction(name, fields, output) {
68
58
  output.push("");
69
59
  output.push(`/** Zero an index within the ${name} structure of arrays. */`);
70
60
  output.push(`export function zero${capitalize(name)}At(i: number) {`);
@@ -74,3 +64,15 @@ function addFieldZeroAtIndexFunction(name, fields, output) {
74
64
  }
75
65
  output.push("}");
76
66
  }
67
+ function addPrintAtIndexFunction(name, fields, output) {
68
+ output.push("");
69
+ output.push(`/** Print an index within the ${name} structure of arrays to the console. */`);
70
+ output.push(`export function print${capitalize(name)}At(i: number) {`);
71
+ output.push(" console.table({");
72
+ for (const field of fields) {
73
+ const [fieldName] = field.split(" ");
74
+ output.push(` ${fieldName}: ${fieldName}[i],`);
75
+ }
76
+ output.push(" })");
77
+ output.push("}");
78
+ }
package/dist/main.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
3
  import { marked } from "marked";
4
- import { Type } from "./consts.js";
5
4
  import { addGroup } from "./lib/group.js";
6
5
  import { addStructureOfArrays } from "./lib/soa.js";
7
6
  const inputFile = process.argv[2];
@@ -38,14 +37,12 @@ for (const token of tokens) {
38
37
  }
39
38
  }
40
39
  for (const { header, fields } of blocks) {
41
- const [, type] = header.split(" ");
42
- switch (type) {
43
- case Type.GROUP:
44
- addGroup(header, fields, output);
45
- break;
46
- case Type.SOA:
47
- addStructureOfArrays(header, fields, output);
48
- break;
40
+ const [, length] = header.split(" ");
41
+ if (length) {
42
+ addStructureOfArrays(header, fields, output);
43
+ }
44
+ else {
45
+ addGroup(header, fields, output);
49
46
  }
50
47
  }
51
48
  fs.writeFileSync(outputFile, output.join("\n"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "game-data-gen",
3
- "version": "4.0.1",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"