@dcl/ecs 7.0.6-4177592674.commit-39cdc99 → 7.0.6-4183962432.commit-2fbe270

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.
Files changed (89) hide show
  1. package/dist/components/extended/Animator.d.ts +3 -2
  2. package/dist/components/extended/Material.d.ts +2 -2
  3. package/dist/components/extended/MeshCollider.d.ts +2 -2
  4. package/dist/components/extended/MeshRenderer.d.ts +2 -2
  5. package/dist/components/generated/Animator.gen.js +6 -0
  6. package/dist/components/generated/AudioSource.gen.js +6 -0
  7. package/dist/components/generated/AudioStream.gen.js +6 -0
  8. package/dist/components/generated/AvatarAttach.gen.js +6 -0
  9. package/dist/components/generated/AvatarModifierArea.gen.js +6 -0
  10. package/dist/components/generated/AvatarShape.gen.js +6 -0
  11. package/dist/components/generated/Billboard.gen.js +6 -0
  12. package/dist/components/generated/CameraMode.gen.js +6 -0
  13. package/dist/components/generated/CameraModeArea.gen.js +6 -0
  14. package/dist/components/generated/GltfContainer.gen.js +6 -0
  15. package/dist/components/generated/Material.gen.js +6 -0
  16. package/dist/components/generated/MeshCollider.gen.js +6 -0
  17. package/dist/components/generated/MeshRenderer.gen.js +6 -0
  18. package/dist/components/generated/NftShape.gen.js +6 -0
  19. package/dist/components/generated/PointerEvents.gen.js +6 -0
  20. package/dist/components/generated/PointerEventsResult.gen.js +6 -0
  21. package/dist/components/generated/PointerLock.gen.js +6 -0
  22. package/dist/components/generated/Raycast.gen.js +6 -0
  23. package/dist/components/generated/RaycastResult.gen.js +6 -0
  24. package/dist/components/generated/TextShape.gen.js +6 -0
  25. package/dist/components/generated/UiBackground.gen.js +6 -0
  26. package/dist/components/generated/UiDropdown.gen.js +6 -0
  27. package/dist/components/generated/UiDropdownResult.gen.js +6 -0
  28. package/dist/components/generated/UiInput.gen.js +6 -0
  29. package/dist/components/generated/UiInputResult.gen.js +6 -0
  30. package/dist/components/generated/UiText.gen.js +6 -0
  31. package/dist/components/generated/UiTransform.gen.js +6 -0
  32. package/dist/components/generated/VideoPlayer.gen.js +6 -0
  33. package/dist/components/generated/VisibilityComponent.gen.js +6 -0
  34. package/dist/components/generated/global.gen.d.ts +26 -26
  35. package/dist/components/generated/index.gen.d.ts +32 -31
  36. package/dist/components/generated/index.gen.js +4 -1
  37. package/dist/components/generated/pb/decentraland/sdk/components/pointer_events_result.gen.d.ts +1 -9
  38. package/dist/components/generated/pb/decentraland/sdk/components/pointer_events_result.gen.js +2 -36
  39. package/dist/components/index.d.ts +9 -9
  40. package/dist/components/index.js +1 -1
  41. package/dist/components/{legacy → manual}/Transform.d.ts +2 -2
  42. package/dist/components/{legacy → manual}/Transform.js +32 -0
  43. package/dist/components/types.d.ts +1 -1
  44. package/dist/engine/component.d.ts +74 -27
  45. package/dist/engine/component.js +7 -240
  46. package/dist/engine/grow-only-value-set-component-definition.d.ts +8 -0
  47. package/dist/engine/grow-only-value-set-component-definition.js +132 -0
  48. package/dist/engine/index.d.ts +1 -2
  49. package/dist/engine/index.js +18 -1
  50. package/dist/engine/input.d.ts +3 -3
  51. package/dist/engine/input.js +75 -68
  52. package/dist/engine/lww-element-set-component-definition.d.ts +6 -0
  53. package/dist/engine/lww-element-set-component-definition.js +229 -0
  54. package/dist/engine/readonly.d.ts +2 -2
  55. package/dist/engine/types.d.ts +20 -9
  56. package/dist/engine/types.js +1 -1
  57. package/dist/runtime/invariant.d.ts +1 -0
  58. package/dist/runtime/invariant.js +6 -1
  59. package/dist/runtime/types.d.ts +1 -2
  60. package/dist/runtime/types.js +0 -1
  61. package/dist/schemas/Array.js +5 -0
  62. package/dist/schemas/ISchema.d.ts +27 -2
  63. package/dist/schemas/Map.d.ts +0 -1
  64. package/dist/schemas/Map.js +9 -0
  65. package/dist/schemas/Optional.js +5 -0
  66. package/dist/schemas/basic/Boolean.js +4 -0
  67. package/dist/schemas/basic/Enum.js +27 -0
  68. package/dist/schemas/basic/Float.js +8 -0
  69. package/dist/schemas/basic/Integer.js +16 -0
  70. package/dist/schemas/basic/String.js +4 -0
  71. package/dist/schemas/buildSchema.d.ts +7 -0
  72. package/dist/schemas/buildSchema.js +63 -0
  73. package/dist/schemas/custom/Color3.js +9 -0
  74. package/dist/schemas/custom/Color4.js +10 -0
  75. package/dist/schemas/custom/Entity.js +4 -0
  76. package/dist/schemas/custom/Quaternion.js +10 -0
  77. package/dist/schemas/custom/Vector3.js +10 -0
  78. package/dist/schemas/index.d.ts +2 -2
  79. package/dist/serialization/crdt/appendValue.d.ts +1 -0
  80. package/dist/serialization/crdt/appendValue.js +49 -0
  81. package/dist/serialization/crdt/index.d.ts +1 -0
  82. package/dist/serialization/crdt/index.js +1 -0
  83. package/dist/serialization/crdt/message.js +4 -0
  84. package/dist/serialization/crdt/types.d.ts +24 -3
  85. package/dist/serialization/crdt/types.js +2 -1
  86. package/dist/systems/crdt/index.d.ts +1 -1
  87. package/dist/systems/crdt/index.js +21 -8
  88. package/dist/systems/events.d.ts +2 -2
  89. package/package.json +3 -3
@@ -0,0 +1,132 @@
1
+ import { ReadWriteByteBuffer } from '../serialization/ByteBuffer';
2
+ import { CrdtMessageType } from '../serialization/crdt';
3
+ import { __DEV__ } from '../runtime/invariant';
4
+ const emptyReadonlySet = freezeSet(new Set());
5
+ function frozenError() {
6
+ throw new Error('The set is frozen');
7
+ }
8
+ function freezeSet(set) {
9
+ ;
10
+ set.add = frozenError;
11
+ set.clear = frozenError;
12
+ return set;
13
+ }
14
+ function sortByTimestamp(a, b) {
15
+ return a.timestamp > b.timestamp ? 1 : -1;
16
+ }
17
+ /**
18
+ * @internal
19
+ */
20
+ export function createValueSetComponentDefinitionFromSchema(componentName, componentId, schema, options) {
21
+ const data = new Map();
22
+ const dirtyIterator = new Set();
23
+ const queuedCommands = [];
24
+ // only sort the array if the latest (N) element has a timestamp <= N-1
25
+ function shouldSort(row) {
26
+ const len = row.raw.length;
27
+ if (len > 1 && row.raw[len - 1].timestamp <= row.raw[len - 2].timestamp) {
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ function gotUpdated(entity) {
33
+ const row = data.get(entity);
34
+ /* istanbul ignore else */
35
+ if (row) {
36
+ if (shouldSort(row)) {
37
+ row.raw.sort(sortByTimestamp);
38
+ }
39
+ while (row.raw.length > options.maxElements) {
40
+ row.raw.shift();
41
+ }
42
+ const frozenSet = freezeSet(new Set(row?.raw.map(($) => $.value)));
43
+ row.frozenSet = frozenSet;
44
+ return frozenSet;
45
+ }
46
+ else {
47
+ /* istanbul ignore next */
48
+ return emptyReadonlySet;
49
+ }
50
+ }
51
+ function append(entity, value) {
52
+ let row = data.get(entity);
53
+ if (!row) {
54
+ row = { raw: [], frozenSet: emptyReadonlySet };
55
+ data.set(entity, row);
56
+ }
57
+ const usedValue = schema.extend ? schema.extend(value) : value;
58
+ const timestamp = options.timestampFunction(usedValue);
59
+ if (__DEV__) {
60
+ // only freeze the objects in dev mode to warn the developers because
61
+ // it is an expensive operation
62
+ Object.freeze(usedValue);
63
+ }
64
+ row.raw.push({ value: usedValue, timestamp });
65
+ return { set: gotUpdated(entity), value: usedValue };
66
+ }
67
+ const ret = {
68
+ get componentId() {
69
+ return componentId;
70
+ },
71
+ get componentName() {
72
+ return componentName;
73
+ },
74
+ get componentType() {
75
+ // a getter is used here to prevent accidental changes
76
+ return 1 /* ComponentType.GrowOnlyValueSet */;
77
+ },
78
+ schema,
79
+ has(entity) {
80
+ return data.has(entity);
81
+ },
82
+ entityDeleted(entity) {
83
+ data.delete(entity);
84
+ },
85
+ get(entity) {
86
+ const values = data.get(entity);
87
+ if (values) {
88
+ return values.frozenSet;
89
+ }
90
+ else {
91
+ return emptyReadonlySet;
92
+ }
93
+ },
94
+ addValue(entity, rawValue) {
95
+ const { set, value } = append(entity, rawValue);
96
+ dirtyIterator.add(entity);
97
+ const buf = new ReadWriteByteBuffer();
98
+ schema.serialize(value, buf);
99
+ queuedCommands.push({
100
+ componentId,
101
+ data: buf.toBinary(),
102
+ entityId: entity,
103
+ timestamp: 0,
104
+ type: CrdtMessageType.APPEND_VALUE
105
+ });
106
+ return set;
107
+ },
108
+ *iterator() {
109
+ for (const [entity, component] of data) {
110
+ yield [entity, component.frozenSet];
111
+ }
112
+ },
113
+ *dirtyIterator() {
114
+ for (const entity of dirtyIterator) {
115
+ yield entity;
116
+ }
117
+ },
118
+ getCrdtUpdates() {
119
+ // return a copy of the commands, and then clear the local copy
120
+ dirtyIterator.clear();
121
+ return queuedCommands.splice(0, queuedCommands.length);
122
+ },
123
+ updateFromCrdt(_body) {
124
+ if (_body.type === CrdtMessageType.APPEND_VALUE) {
125
+ const buf = new ReadWriteByteBuffer(_body.data);
126
+ append(_body.entityId, schema.deserialize(buf));
127
+ }
128
+ return [null, undefined];
129
+ }
130
+ };
131
+ return ret;
132
+ }
@@ -1,9 +1,8 @@
1
1
  import { ByteBuffer } from '../serialization/ByteBuffer';
2
2
  import { OnChangeFunction } from '../systems/crdt';
3
- import { ComponentDefinition } from './component';
4
3
  import { Entity } from './entity';
5
4
  import { SystemItem } from './systems';
6
5
  export * from './input';
7
6
  export * from './readonly';
8
7
  export * from './types';
9
- export { Entity, ByteBuffer, ComponentDefinition, SystemItem, OnChangeFunction };
8
+ export { Entity, ByteBuffer, SystemItem, OnChangeFunction };
@@ -3,9 +3,10 @@ import { componentNumberFromName } from '../components/component-number';
3
3
  import { checkNotThenable } from '../runtime/invariant';
4
4
  import { Schemas } from '../schemas';
5
5
  import { crdtSceneSystem } from '../systems/crdt';
6
- import { createComponentDefinitionFromSchema } from './component';
6
+ import { createComponentDefinitionFromSchema } from './lww-element-set-component-definition';
7
7
  import { EntityContainer } from './entity';
8
8
  import { SystemContainer, SYSTEMS_REGULAR_PRIORITY } from './systems';
9
+ import { createValueSetComponentDefinitionFromSchema } from './grow-only-value-set-component-definition';
9
10
  export * from './input';
10
11
  export * from './readonly';
11
12
  export * from './types';
@@ -64,6 +65,20 @@ function preEngine() {
64
65
  componentsDefinition.set(componentId, newComponent);
65
66
  return newComponent;
66
67
  }
68
+ function defineValueSetComponentFromSchema(componentName, schema, options) {
69
+ /* istanbul ignore next */
70
+ if (sealed)
71
+ throw new Error('Engine is already sealed. No components can be added at this stage');
72
+ const componentId = componentNumberFromName(componentName);
73
+ const prev = componentsDefinition.get(componentId);
74
+ if (prev) {
75
+ // TODO: assert spec === prev.spec
76
+ return prev;
77
+ }
78
+ const newComponent = createValueSetComponentDefinitionFromSchema(componentName, componentId, schema, options);
79
+ componentsDefinition.set(componentId, newComponent);
80
+ return newComponent;
81
+ }
67
82
  function defineComponent(componentName, mapSpec, constructorDefault) {
68
83
  if (sealed)
69
84
  throw new Error('Engine is already sealed. No components can be added at this stage');
@@ -159,6 +174,7 @@ function preEngine() {
159
174
  removeSystem,
160
175
  defineComponent,
161
176
  defineComponentFromSchema,
177
+ defineValueSetComponentFromSchema,
162
178
  getEntitiesWith,
163
179
  getComponent,
164
180
  getComponentOrNull,
@@ -194,6 +210,7 @@ export function Engine(options) {
194
210
  removeSystem: partialEngine.removeSystem,
195
211
  defineComponent: partialEngine.defineComponent,
196
212
  defineComponentFromSchema: partialEngine.defineComponentFromSchema,
213
+ defineValueSetComponentFromSchema: partialEngine.defineValueSetComponentFromSchema,
197
214
  registerComponentDefinition: partialEngine.registerComponentDefinition,
198
215
  getEntitiesWith: partialEngine.getEntitiesWith,
199
216
  getComponent: partialEngine.getComponent,
@@ -1,6 +1,6 @@
1
1
  import { InputAction } from '../components/generated/pb/decentraland/sdk/components/common/input_action.gen';
2
2
  import { PointerEventType } from '../components/generated/pb/decentraland/sdk/components/pointer_events.gen';
3
- import { PBPointerEventsResult_PointerCommand } from '../components/generated/pb/decentraland/sdk/components/pointer_events_result.gen';
3
+ import { PBPointerEventsResult } from '../components/generated/pb/decentraland/sdk/components/pointer_events_result.gen';
4
4
  import { Entity } from './entity';
5
5
  /**
6
6
  * @public
@@ -14,7 +14,7 @@ export type IInputSystem = {
14
14
  * @param entity - the entity to query, ignore for global
15
15
  * @returns boolean
16
16
  */
17
- isTriggered: (inputAction: InputAction, pointerEventType: PointerEventType, entity?: Entity) => boolean;
17
+ isTriggered: (inputAction: InputAction, pointerEventType: PointerEventType, entity: Entity) => boolean;
18
18
  /**
19
19
  * @public
20
20
  * Check if an input action is currently being pressed.
@@ -30,5 +30,5 @@ export type IInputSystem = {
30
30
  * @param entity - the entity to query, ignore for global
31
31
  * @returns the input command info or undefined if there is no command in the last tick-update
32
32
  */
33
- getInputCommand: (inputAction: InputAction, pointerEventType: PointerEventType, entity?: Entity) => PBPointerEventsResult_PointerCommand | null;
33
+ getInputCommand: (inputAction: InputAction, pointerEventType: PointerEventType, entity: Entity) => PBPointerEventsResult | null;
34
34
  };
@@ -1,5 +1,4 @@
1
1
  import * as components from '../components';
2
- import { Schemas } from '../schemas';
3
2
  const InputCommands = [
4
3
  0 /* InputAction.IA_POINTER */,
5
4
  1 /* InputAction.IA_PRIMARY */,
@@ -15,69 +14,71 @@ const InputCommands = [
15
14
  12 /* InputAction.IA_ACTION_5 */,
16
15
  13 /* InputAction.IA_ACTION_6 */
17
16
  ];
18
- const InternalInputStateSchema = {
19
- timestampLastUpdate: Schemas.Number,
20
- currentTimestamp: Schemas.Number,
21
- buttonState: Schemas.Array(Schemas.Map({
22
- value: Schemas.Boolean,
23
- ts: Schemas.Number
24
- }))
25
- };
26
- const TimestampUpdateSystemPriority = 1 << 20;
27
- const ButtonStateUpdateSystemPriority = 0;
17
+ const InputStateUpdateSystemPriority = 1 << 20;
28
18
  /**
29
19
  * @internal
30
20
  */
31
21
  export function createInputSystem(engine) {
32
22
  const PointerEventsResult = components.PointerEventsResult(engine);
33
- const InternalInputStateComponent = engine.defineComponent('@dcl/sdk/InternalInputStateSchema', InternalInputStateSchema);
34
- InternalInputStateComponent.create(engine.RootEntity, {
35
- buttonState: Array.from({ length: InputCommands.length }, () => ({
36
- ts: 0,
37
- value: false
38
- }))
39
- });
40
- function timestampUpdateSystem() {
41
- const state = InternalInputStateComponent.get(engine.RootEntity);
42
- if (state.currentTimestamp > state.timestampLastUpdate) {
43
- InternalInputStateComponent.getMutable(engine.RootEntity).timestampLastUpdate = state.currentTimestamp;
44
- }
45
- }
46
- function* commandIterator() {
47
- for (const [, value] of engine.getEntitiesWith(PointerEventsResult)) {
48
- yield* value.commands;
23
+ const globalState = {
24
+ previousFrameMaxTimestamp: 0,
25
+ currentFrameMaxTimestamp: 0,
26
+ buttonState: new Map()
27
+ };
28
+ function findLastAction(pointerEventType, inputAction, entity) {
29
+ const ascendingTimestampIterator = PointerEventsResult.get(entity);
30
+ for (const command of Array.from(ascendingTimestampIterator).reverse()) {
31
+ if (command.button === inputAction && command.state === pointerEventType) {
32
+ return command;
33
+ }
49
34
  }
50
35
  }
51
- function findLastAction(pointerEventType, inputAction, entity) {
52
- let commandToReturn = undefined;
53
- for (const command of commandIterator()) {
54
- if (command.button === inputAction &&
55
- command.state === pointerEventType &&
56
- (!entity || (command.hit && entity === command.hit.entityId))) {
57
- if (!commandToReturn || command.timestamp >= commandToReturn.timestamp)
58
- commandToReturn = command;
36
+ function* findCommandsByActionDescending(inputAction, entity) {
37
+ const ascendingTimestampIterator = PointerEventsResult.get(entity);
38
+ for (const command of Array.from(ascendingTimestampIterator).reverse()) {
39
+ if (command.button === inputAction) {
40
+ yield command;
59
41
  }
60
42
  }
61
- return commandToReturn;
62
43
  }
63
44
  function buttonStateUpdateSystem() {
64
- const component = PointerEventsResult.getOrNull(engine.RootEntity);
65
- if (!component)
66
- return;
67
- const state = InternalInputStateComponent.getMutable(engine.RootEntity);
68
- for (const command of commandIterator()) {
69
- if (command.timestamp > state.buttonState[command.button].ts) {
70
- if (command.state === 1 /* PointerEventType.PET_DOWN */) {
71
- state.buttonState[command.button].value = true;
45
+ // first store the previous' frame timestamp
46
+ let maxTimestamp = globalState.currentFrameMaxTimestamp;
47
+ globalState.previousFrameMaxTimestamp = maxTimestamp;
48
+ // then iterate over all new commands
49
+ for (const [, commands] of engine.getEntitiesWith(PointerEventsResult)) {
50
+ // TODO: adapt the gset component to have a cached "reversed" option by default
51
+ const arrayCommands = Array.from(commands);
52
+ for (let i = arrayCommands.length - 1; i >= 0; i--) {
53
+ const command = arrayCommands[i];
54
+ if (command.timestamp > maxTimestamp) {
55
+ maxTimestamp = command.timestamp;
72
56
  }
73
- else if (command.state === 0 /* PointerEventType.PET_UP */) {
74
- state.buttonState[command.button].value = false;
57
+ if (command.state === 0 /* PointerEventType.PET_UP */ || command.state === 1 /* PointerEventType.PET_DOWN */) {
58
+ const prevCommand = globalState.buttonState.get(command.button);
59
+ if (!prevCommand || command.timestamp > prevCommand.timestamp) {
60
+ globalState.buttonState.set(command.button, command);
61
+ }
62
+ else {
63
+ // since we are iterating a descending array, we can early finish the
64
+ // loop
65
+ break;
66
+ }
75
67
  }
76
68
  }
77
69
  }
70
+ // update current frame's max timestamp
71
+ globalState.currentFrameMaxTimestamp = maxTimestamp;
72
+ }
73
+ engine.addSystem(buttonStateUpdateSystem, InputStateUpdateSystemPriority, '@dcl/ecs#inputSystem');
74
+ function timestampIsCurrentFrame(timestamp) {
75
+ if (timestamp > globalState.previousFrameMaxTimestamp && timestamp <= globalState.currentFrameMaxTimestamp) {
76
+ return true;
77
+ }
78
+ else {
79
+ return false;
80
+ }
78
81
  }
79
- engine.addSystem(buttonStateUpdateSystem, ButtonStateUpdateSystemPriority, '@dcl/ecs#buttonStateUpdateSystem');
80
- engine.addSystem(timestampUpdateSystem, TimestampUpdateSystemPriority, '@dcl/ecs#timestampUpdateSystem');
81
82
  function getClick(inputAction, entity) {
82
83
  if (inputAction !== 3 /* InputAction.IA_ANY */) {
83
84
  return findClick(inputAction, entity);
@@ -90,18 +91,27 @@ export function createInputSystem(engine) {
90
91
  return null;
91
92
  }
92
93
  function findClick(inputAction, entity) {
93
- // We search the last DOWN command sorted by timestamp
94
- const down = findLastAction(1 /* PointerEventType.PET_DOWN */, inputAction, entity);
95
- // We search the last UP command sorted by timestamp
96
- if (!down)
97
- return null;
98
- const up = findLastAction(0 /* PointerEventType.PET_UP */, inputAction, entity);
99
- if (!up)
94
+ let down = null;
95
+ let up = null;
96
+ // We search the last UP & DOWN command sorted by timestamp descending
97
+ for (const it of findCommandsByActionDescending(inputAction, entity)) {
98
+ if (!up) {
99
+ if (it.state === 0 /* PointerEventType.PET_UP */) {
100
+ up = it;
101
+ continue;
102
+ }
103
+ }
104
+ else if (!down) {
105
+ if (it.state === 1 /* PointerEventType.PET_DOWN */) {
106
+ down = it;
107
+ break;
108
+ }
109
+ }
110
+ }
111
+ if (!up || !down)
100
112
  return null;
101
- const state = InternalInputStateComponent.get(engine.RootEntity);
102
113
  // If the DOWN command has happen before the UP commands, it means that that a clicked has happen
103
- if (down.timestamp < up.timestamp && up.timestamp > state.timestampLastUpdate) {
104
- InternalInputStateComponent.getMutable(engine.RootEntity).currentTimestamp = Math.max(up.timestamp, state.currentTimestamp);
114
+ if (down.timestamp < up.timestamp && timestampIsCurrentFrame(up.timestamp)) {
105
115
  return { up, down };
106
116
  }
107
117
  return null;
@@ -122,34 +132,31 @@ export function createInputSystem(engine) {
122
132
  const command = findLastAction(pointerEventType, inputAction, entity);
123
133
  if (!command)
124
134
  return null;
125
- const state = InternalInputStateComponent.get(engine.RootEntity);
126
- if (command.timestamp > state.timestampLastUpdate) {
127
- InternalInputStateComponent.getMutable(engine.RootEntity).currentTimestamp = Math.max(command.timestamp, state.currentTimestamp);
135
+ if (timestampIsCurrentFrame(command.timestamp)) {
128
136
  return command;
129
137
  }
130
138
  else {
131
139
  return null;
132
140
  }
133
141
  }
142
+ // returns true if there was a DOWN (in any past frame), and then an UP in the last frame
134
143
  function isClicked(inputAction, entity) {
135
144
  return getClick(inputAction, entity) !== null;
136
145
  }
146
+ // returns true if the provided last action was triggered in the last frame
137
147
  function isTriggered(inputAction, pointerEventType, entity) {
138
- return getInputCommand(inputAction, pointerEventType, entity) !== null;
148
+ const command = findLastAction(pointerEventType, inputAction, entity);
149
+ return (command && timestampIsCurrentFrame(command.timestamp)) || false;
139
150
  }
151
+ // returns the global state of the input. This global state is updated from the system
140
152
  function isPressed(inputAction) {
141
- return InternalInputStateComponent.get(engine.RootEntity).buttonState[inputAction].value;
153
+ return globalState.buttonState.get(inputAction)?.state === 1 /* PointerEventType.PET_DOWN */;
142
154
  }
143
155
  return {
144
- // @public
145
156
  isPressed,
146
- // @internal
147
157
  getClick,
148
- // @public
149
158
  getInputCommand,
150
- // @internal
151
159
  isClicked,
152
- // @public
153
160
  isTriggered
154
161
  };
155
162
  }
@@ -0,0 +1,6 @@
1
+ import { ISchema } from '../schemas';
2
+ import { PutComponentMessageBody, DeleteComponentMessageBody, CrdtMessageBody } from '../serialization/crdt';
3
+ import { Entity } from './entity';
4
+ export declare function incrementTimestamp(entity: Entity, timestamps: Map<Entity, number>): number;
5
+ export declare function createUpdateLwwFromCrdt(componentId: number, timestamps: Map<Entity, number>, schema: Pick<ISchema<any>, 'serialize' | 'deserialize'>, data: Map<Entity, unknown>): (msg: CrdtMessageBody) => [null | PutComponentMessageBody | DeleteComponentMessageBody, any];
6
+ export declare function createGetCrdtMessagesForLww(componentId: number, timestamps: Map<Entity, number>, dirtyIterator: Set<Entity>, schema: Pick<ISchema<any>, 'serialize'>, data: Map<Entity, unknown>): () => Generator<PutComponentMessageBody | DeleteComponentMessageBody, void, unknown>;
@@ -0,0 +1,229 @@
1
+ import { ReadWriteByteBuffer } from '../serialization/ByteBuffer';
2
+ import { ProcessMessageResultType, CrdtMessageType } from '../serialization/crdt';
3
+ import { dataCompare } from '../systems/crdt/utils';
4
+ import { deepReadonly } from './readonly';
5
+ export function incrementTimestamp(entity, timestamps) {
6
+ const newTimestamp = (timestamps.get(entity) || 0) + 1;
7
+ timestamps.set(entity, newTimestamp);
8
+ return newTimestamp;
9
+ }
10
+ export function createUpdateLwwFromCrdt(componentId, timestamps, schema, data) {
11
+ /**
12
+ * Process the received message only if the lamport number recieved is higher
13
+ * than the stored one. If its lower, we spread it to the network to correct the peer.
14
+ * If they are equal, the bigger raw data wins.
15
+
16
+ * Returns the recieved data if the lamport number was bigger than ours.
17
+ * If it was an outdated message, then we return void
18
+ * @public
19
+ */
20
+ function crdtRuleForCurrentState(message) {
21
+ const { entityId, timestamp } = message;
22
+ const currentTimestamp = timestamps.get(entityId);
23
+ // The received message is > than our current value, update our state.components.
24
+ if (currentTimestamp === undefined || currentTimestamp < timestamp) {
25
+ return ProcessMessageResultType.StateUpdatedTimestamp;
26
+ }
27
+ // Outdated Message. Resend our state message through the wire.
28
+ if (currentTimestamp > timestamp) {
29
+ // console.log('2', currentTimestamp, timestamp)
30
+ return ProcessMessageResultType.StateOutdatedTimestamp;
31
+ }
32
+ // Deletes are idempotent
33
+ if (message.type === CrdtMessageType.DELETE_COMPONENT && !data.has(entityId)) {
34
+ return ProcessMessageResultType.NoChanges;
35
+ }
36
+ let currentDataGreater = 0;
37
+ if (data.has(entityId)) {
38
+ const writeBuffer = new ReadWriteByteBuffer();
39
+ schema.serialize(data.get(entityId), writeBuffer);
40
+ currentDataGreater = dataCompare(writeBuffer.toBinary(), message.data || null);
41
+ }
42
+ else {
43
+ currentDataGreater = dataCompare(null, message.data);
44
+ }
45
+ // Same data, same timestamp. Weirdo echo message.
46
+ // console.log('3', currentDataGreater, writeBuffer.toBinary(), (message as any).data || null)
47
+ if (currentDataGreater === 0) {
48
+ return ProcessMessageResultType.NoChanges;
49
+ }
50
+ else if (currentDataGreater > 0) {
51
+ // Current data is greater
52
+ return ProcessMessageResultType.StateOutdatedData;
53
+ }
54
+ else {
55
+ // Curent data is lower
56
+ return ProcessMessageResultType.StateUpdatedData;
57
+ }
58
+ }
59
+ return (msg) => {
60
+ /* istanbul ignore next */
61
+ if (msg.type !== CrdtMessageType.PUT_COMPONENT && msg.type !== CrdtMessageType.DELETE_COMPONENT)
62
+ /* istanbul ignore next */
63
+ return [null, data.get(msg.entityId)];
64
+ const action = crdtRuleForCurrentState(msg);
65
+ const entity = msg.entityId;
66
+ switch (action) {
67
+ case ProcessMessageResultType.StateUpdatedData:
68
+ case ProcessMessageResultType.StateUpdatedTimestamp: {
69
+ timestamps.set(entity, msg.timestamp);
70
+ if (msg.type === CrdtMessageType.PUT_COMPONENT) {
71
+ const buf = new ReadWriteByteBuffer(msg.data);
72
+ data.set(entity, schema.deserialize(buf));
73
+ }
74
+ else {
75
+ data.delete(entity);
76
+ }
77
+ return [null, data.get(entity)];
78
+ }
79
+ case ProcessMessageResultType.StateOutdatedTimestamp:
80
+ case ProcessMessageResultType.StateOutdatedData: {
81
+ if (data.has(entity)) {
82
+ const writeBuffer = new ReadWriteByteBuffer();
83
+ schema.serialize(data.get(entity), writeBuffer);
84
+ return [
85
+ {
86
+ type: CrdtMessageType.PUT_COMPONENT,
87
+ componentId,
88
+ data: writeBuffer.toBinary(),
89
+ entityId: entity,
90
+ timestamp: timestamps.get(entity)
91
+ },
92
+ data.get(entity)
93
+ ];
94
+ }
95
+ else {
96
+ return [
97
+ {
98
+ type: CrdtMessageType.DELETE_COMPONENT,
99
+ componentId,
100
+ entityId: entity,
101
+ timestamp: timestamps.get(entity)
102
+ },
103
+ undefined
104
+ ];
105
+ }
106
+ }
107
+ }
108
+ return [null, data.get(entity)];
109
+ };
110
+ }
111
+ export function createGetCrdtMessagesForLww(componentId, timestamps, dirtyIterator, schema, data) {
112
+ return function* () {
113
+ for (const entity of dirtyIterator) {
114
+ const newTimestamp = incrementTimestamp(entity, timestamps);
115
+ if (data.has(entity)) {
116
+ const writeBuffer = new ReadWriteByteBuffer();
117
+ schema.serialize(data.get(entity), writeBuffer);
118
+ const msg = {
119
+ type: CrdtMessageType.PUT_COMPONENT,
120
+ componentId,
121
+ entityId: entity,
122
+ data: writeBuffer.toBinary(),
123
+ timestamp: newTimestamp
124
+ };
125
+ yield msg;
126
+ }
127
+ else {
128
+ const msg = {
129
+ type: CrdtMessageType.DELETE_COMPONENT,
130
+ componentId,
131
+ entityId: entity,
132
+ timestamp: newTimestamp
133
+ };
134
+ yield msg;
135
+ }
136
+ }
137
+ dirtyIterator.clear();
138
+ };
139
+ }
140
+ /**
141
+ * @internal
142
+ */
143
+ export function createComponentDefinitionFromSchema(componentName, componentId, schema) {
144
+ const data = new Map();
145
+ const dirtyIterator = new Set();
146
+ const timestamps = new Map();
147
+ return {
148
+ get componentId() {
149
+ return componentId;
150
+ },
151
+ get componentName() {
152
+ return componentName;
153
+ },
154
+ get componentType() {
155
+ // a getter is used here to prevent accidental changes
156
+ return 0 /* ComponentType.LastWriteWinElementSet */;
157
+ },
158
+ schema,
159
+ has(entity) {
160
+ return data.has(entity);
161
+ },
162
+ deleteFrom(entity, markAsDirty = true) {
163
+ const component = data.get(entity);
164
+ if (data.delete(entity) && markAsDirty) {
165
+ dirtyIterator.add(entity);
166
+ }
167
+ return component || null;
168
+ },
169
+ entityDeleted(entity, markAsDirty) {
170
+ if (data.delete(entity) && markAsDirty) {
171
+ dirtyIterator.add(entity);
172
+ }
173
+ },
174
+ getOrNull(entity) {
175
+ const component = data.get(entity);
176
+ return component ? deepReadonly(component) : null;
177
+ },
178
+ get(entity) {
179
+ const component = data.get(entity);
180
+ if (!component) {
181
+ throw new Error(`[getFrom] Component ${componentName} for entity #${entity} not found`);
182
+ }
183
+ return deepReadonly(component);
184
+ },
185
+ create(entity, value) {
186
+ const component = data.get(entity);
187
+ if (component) {
188
+ throw new Error(`[create] Component ${componentName} for ${entity} already exists`);
189
+ }
190
+ const usedValue = value === undefined ? schema.create() : schema.extend ? schema.extend(value) : value;
191
+ data.set(entity, usedValue);
192
+ dirtyIterator.add(entity);
193
+ return usedValue;
194
+ },
195
+ createOrReplace(entity, value) {
196
+ const usedValue = value === undefined ? schema.create() : schema.extend ? schema.extend(value) : value;
197
+ data.set(entity, usedValue);
198
+ dirtyIterator.add(entity);
199
+ return usedValue;
200
+ },
201
+ getMutableOrNull(entity) {
202
+ const component = data.get(entity);
203
+ if (!component) {
204
+ return null;
205
+ }
206
+ dirtyIterator.add(entity);
207
+ return component;
208
+ },
209
+ getMutable(entity) {
210
+ const component = this.getMutableOrNull(entity);
211
+ if (component === null) {
212
+ throw new Error(`[mutable] Component ${componentName} for ${entity} not found`);
213
+ }
214
+ return component;
215
+ },
216
+ *iterator() {
217
+ for (const [entity, component] of data) {
218
+ yield [entity, component];
219
+ }
220
+ },
221
+ *dirtyIterator() {
222
+ for (const entity of dirtyIterator) {
223
+ yield entity;
224
+ }
225
+ },
226
+ getCrdtUpdates: createGetCrdtMessagesForLww(componentId, timestamps, dirtyIterator, schema, data),
227
+ updateFromCrdt: createUpdateLwwFromCrdt(componentId, timestamps, schema, data)
228
+ };
229
+ }
@@ -2,7 +2,7 @@ import { ComponentDefinition } from './component';
2
2
  /**
3
3
  * @public
4
4
  */
5
- export type DeepReadonlyMap<K, V> = ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>;
5
+ export type DeepReadonlyMap<K, V> = ReadonlyMap<K, DeepReadonly<V>>;
6
6
  /**
7
7
  * @public
8
8
  */
@@ -26,4 +26,4 @@ export type ReadonlyComponentSchema<T extends [ComponentDefinition<unknown>, ...
26
26
  /**
27
27
  * @public
28
28
  */
29
- export type DeepReadonly<T> = T extends ReadonlyPrimitive ? T : T extends Map<infer K, infer V> ? DeepReadonlyMap<K, V> : T extends Set<infer M> ? DeepReadonlySet<M> : DeepReadonlyObject<T>;
29
+ export type DeepReadonly<T> = T extends ReadonlyPrimitive ? T : T extends Array<infer K> ? ReadonlyArray<DeepReadonly<K>> : T extends Map<infer K, infer V> ? DeepReadonlyMap<K, V> : T extends Set<infer M> ? DeepReadonlySet<M> : DeepReadonlyObject<T>;