@dcl/ecs 7.0.6-4177592674.commit-39cdc99 → 7.0.6-4180146485.commit-9a7dde9
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/dist/components/extended/Animator.d.ts +3 -2
- package/dist/components/extended/Material.d.ts +2 -2
- package/dist/components/extended/MeshCollider.d.ts +2 -2
- package/dist/components/extended/MeshRenderer.d.ts +2 -2
- package/dist/components/generated/global.gen.d.ts +26 -26
- package/dist/components/generated/index.gen.d.ts +32 -31
- package/dist/components/generated/index.gen.js +4 -1
- package/dist/components/generated/pb/decentraland/sdk/components/pointer_events_result.gen.d.ts +1 -9
- package/dist/components/generated/pb/decentraland/sdk/components/pointer_events_result.gen.js +2 -36
- package/dist/components/index.d.ts +9 -9
- package/dist/components/index.js +1 -1
- package/dist/components/{legacy → manual}/Transform.d.ts +2 -2
- package/dist/components/{legacy → manual}/Transform.js +0 -0
- package/dist/components/types.d.ts +1 -1
- package/dist/engine/component.d.ts +74 -27
- package/dist/engine/component.js +7 -240
- package/dist/engine/grow-only-value-set-component-definition.d.ts +8 -0
- package/dist/engine/grow-only-value-set-component-definition.js +132 -0
- package/dist/engine/index.d.ts +1 -2
- package/dist/engine/index.js +18 -1
- package/dist/engine/input.d.ts +3 -3
- package/dist/engine/input.js +75 -68
- package/dist/engine/lww-element-set-component-definition.d.ts +6 -0
- package/dist/engine/lww-element-set-component-definition.js +229 -0
- package/dist/engine/readonly.d.ts +2 -2
- package/dist/engine/types.d.ts +20 -9
- package/dist/engine/types.js +1 -1
- package/dist/runtime/invariant.d.ts +1 -0
- package/dist/runtime/invariant.js +6 -1
- package/dist/runtime/types.d.ts +1 -2
- package/dist/runtime/types.js +0 -1
- package/dist/schemas/ISchema.d.ts +3 -2
- package/dist/schemas/Map.d.ts +0 -1
- package/dist/serialization/crdt/appendValue.d.ts +1 -0
- package/dist/serialization/crdt/appendValue.js +49 -0
- package/dist/serialization/crdt/index.d.ts +1 -0
- package/dist/serialization/crdt/index.js +1 -0
- package/dist/serialization/crdt/message.js +4 -0
- package/dist/serialization/crdt/types.d.ts +24 -3
- package/dist/serialization/crdt/types.js +2 -1
- package/dist/systems/crdt/index.d.ts +1 -1
- package/dist/systems/crdt/index.js +21 -8
- package/dist/systems/events.d.ts +2 -2
- package/package.json +3 -3
package/dist/engine/component.js
CHANGED
|
@@ -1,242 +1,9 @@
|
|
|
1
|
-
import { ReadWriteByteBuffer } from '../serialization/ByteBuffer';
|
|
2
|
-
import { CrdtMessageType, ProcessMessageResultType } 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 createUpdateFromCrdt(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 createGetCrdtMessages(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
1
|
/**
|
|
141
|
-
*
|
|
2
|
+
* Component types are used to pick the wire protocol and the conflict resolution algorithm
|
|
3
|
+
* @public
|
|
142
4
|
*/
|
|
143
|
-
export
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
get componentId() {
|
|
149
|
-
return componentId;
|
|
150
|
-
},
|
|
151
|
-
get componentName() {
|
|
152
|
-
return componentName;
|
|
153
|
-
},
|
|
154
|
-
default() {
|
|
155
|
-
return schema.create();
|
|
156
|
-
},
|
|
157
|
-
isDirty(entity) {
|
|
158
|
-
return dirtyIterator.has(entity);
|
|
159
|
-
},
|
|
160
|
-
has(entity) {
|
|
161
|
-
return data.has(entity);
|
|
162
|
-
},
|
|
163
|
-
deleteFrom(entity, markAsDirty = true) {
|
|
164
|
-
const component = data.get(entity);
|
|
165
|
-
if (data.delete(entity) && markAsDirty) {
|
|
166
|
-
dirtyIterator.add(entity);
|
|
167
|
-
}
|
|
168
|
-
return component || null;
|
|
169
|
-
},
|
|
170
|
-
entityDeleted(entity, markAsDirty) {
|
|
171
|
-
if (data.delete(entity) && markAsDirty) {
|
|
172
|
-
dirtyIterator.add(entity);
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
getOrNull(entity) {
|
|
176
|
-
const component = data.get(entity);
|
|
177
|
-
return component ? deepReadonly(component) : null;
|
|
178
|
-
},
|
|
179
|
-
get(entity) {
|
|
180
|
-
const component = data.get(entity);
|
|
181
|
-
if (!component) {
|
|
182
|
-
throw new Error(`[getFrom] Component ${componentName} for entity #${entity} not found`);
|
|
183
|
-
}
|
|
184
|
-
return deepReadonly(component);
|
|
185
|
-
},
|
|
186
|
-
create(entity, value) {
|
|
187
|
-
const component = data.get(entity);
|
|
188
|
-
if (component) {
|
|
189
|
-
throw new Error(`[create] Component ${componentName} for ${entity} already exists`);
|
|
190
|
-
}
|
|
191
|
-
const usedValue = value === undefined ? schema.create() : schema.extend ? schema.extend(value) : value;
|
|
192
|
-
data.set(entity, usedValue);
|
|
193
|
-
dirtyIterator.add(entity);
|
|
194
|
-
return usedValue;
|
|
195
|
-
},
|
|
196
|
-
createOrReplace(entity, value) {
|
|
197
|
-
const usedValue = value === undefined ? schema.create() : schema.extend ? schema.extend(value) : value;
|
|
198
|
-
data.set(entity, usedValue);
|
|
199
|
-
dirtyIterator.add(entity);
|
|
200
|
-
return usedValue;
|
|
201
|
-
},
|
|
202
|
-
getMutableOrNull(entity) {
|
|
203
|
-
const component = data.get(entity);
|
|
204
|
-
if (!component) {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
dirtyIterator.add(entity);
|
|
208
|
-
return component;
|
|
209
|
-
},
|
|
210
|
-
getMutable(entity) {
|
|
211
|
-
const component = this.getMutableOrNull(entity);
|
|
212
|
-
if (component === null) {
|
|
213
|
-
throw new Error(`[mutable] Component ${componentName} for ${entity} not found`);
|
|
214
|
-
}
|
|
215
|
-
return component;
|
|
216
|
-
},
|
|
217
|
-
*iterator() {
|
|
218
|
-
for (const [entity, component] of data) {
|
|
219
|
-
yield [entity, component];
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
*dirtyIterator() {
|
|
223
|
-
for (const entity of dirtyIterator) {
|
|
224
|
-
yield entity;
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
getCrdtUpdates: createGetCrdtMessages(componentId, timestamps, dirtyIterator, schema, data),
|
|
228
|
-
toBinary(entity) {
|
|
229
|
-
const component = data.get(entity);
|
|
230
|
-
if (!component) {
|
|
231
|
-
throw new Error(`[toBinary] Component ${componentName} for ${entity} not found`);
|
|
232
|
-
}
|
|
233
|
-
const writeBuffer = new ReadWriteByteBuffer();
|
|
234
|
-
schema.serialize(component, writeBuffer);
|
|
235
|
-
return writeBuffer;
|
|
236
|
-
},
|
|
237
|
-
updateFromCrdt: createUpdateFromCrdt(componentId, timestamps, schema, data),
|
|
238
|
-
deserialize(buffer) {
|
|
239
|
-
return schema.deserialize(buffer);
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
5
|
+
export var ComponentType;
|
|
6
|
+
(function (ComponentType) {
|
|
7
|
+
ComponentType[ComponentType["LastWriteWinElementSet"] = 0] = "LastWriteWinElementSet";
|
|
8
|
+
ComponentType[ComponentType["GrowOnlyValueSet"] = 1] = "GrowOnlyValueSet";
|
|
9
|
+
})(ComponentType || (ComponentType = {}));
|
|
@@ -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
|
+
}
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -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,
|
|
8
|
+
export { Entity, ByteBuffer, SystemItem, OnChangeFunction };
|
package/dist/engine/index.js
CHANGED
|
@@ -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,
|
package/dist/engine/input.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
33
|
+
getInputCommand: (inputAction: InputAction, pointerEventType: PointerEventType, entity: Entity) => PBPointerEventsResult | null;
|
|
34
34
|
};
|
package/dist/engine/input.js
CHANGED
|
@@ -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
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
for (const command of
|
|
54
|
-
if (command.button === inputAction
|
|
55
|
-
command
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for (const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
// We search the last UP command sorted by timestamp
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|