@@ -5,6 +5,7 @@
|
|
5
5
|
import { IComponent, IContext } from './engine_types';
|
6
6
|
import { isActiveSelf } from './engine_gameobject';
|
7
7
|
import { ContextRegistry } from "./engine_context_registry";
|
8
|
+
import { showBalloonWarning, isDevEnvironment, showBalloonMessage, LogType } from "./debug";
|
8
9
|
|
9
10
|
const debug = getParam("debugnewscripts");
|
10
11
|
const debugHierarchy = getParam("debughierarchy");
|
@@ -171,8 +172,7 @@
|
|
171
172
|
// keep them in queue until script has started
|
172
173
|
// call awake if the script was inactive before
|
173
174
|
utils.safeInvoke(script.__internalAwake.bind(script));
|
174
|
-
if(script.enabled)
|
175
|
-
{
|
175
|
+
if (script.enabled) {
|
176
176
|
utils.safeInvoke(script.__internalEnable.bind(script));
|
177
177
|
// now call start
|
178
178
|
utils.safeInvoke(script.__internalStart.bind(script));
|
@@ -232,8 +232,13 @@
|
|
232
232
|
const activeSelf = isActiveSelf(obj);
|
233
233
|
const wasSuccessful = updateIsActiveInHierarchyRecursiveRuntime(obj, activeSelf, true);
|
234
234
|
if (!wasSuccessful) {
|
235
|
-
|
235
|
+
if (debug || isDevEnvironment()) {
|
236
|
+
console.error("Error updating hierarchy\nDo you have circular references in your project? <a target=\"_blank\" href=\"https://docs.needle.tools/circular-reference\"> Click here for more information.", obj)
|
237
|
+
}
|
238
|
+
else
|
239
|
+
console.error("Failed to update active state in hierarchy of \"" + obj.name + "\"", obj);
|
236
240
|
console.warn(" ↑ this error might be caused by circular references. Please make sure you don't have files with circular references (e.g. one GLB 1 is loading GLB 2 which is then loading GLB 1 again).")
|
241
|
+
|
237
242
|
}
|
238
243
|
}
|
239
244
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { getParam } from "./engine_utils";
|
2
|
-
import {
|
3
|
-
import {
|
2
|
+
import { isDevEnvironment } from "./debug";
|
3
|
+
import { IComponent } from "./engine_types";
|
4
4
|
|
5
5
|
const debug = getParam("debugautosync");
|
6
6
|
|
@@ -8,7 +8,7 @@
|
|
8
8
|
class ComponentsSyncerManager {
|
9
9
|
private _syncers: { [key: string]: ComponentPropertiesSyncer } = {};
|
10
10
|
|
11
|
-
getOrCreateSyncer(comp:
|
11
|
+
getOrCreateSyncer(comp: IComponent): ComponentPropertiesSyncer | null {
|
12
12
|
if (!comp.guid) return null;
|
13
13
|
if (this._syncers[comp.guid]) return this._syncers[comp.guid];
|
14
14
|
const syncer = new ComponentPropertiesSyncer(comp);
|
@@ -28,9 +28,9 @@
|
|
28
28
|
*/
|
29
29
|
class ComponentPropertiesSyncer {
|
30
30
|
|
31
|
-
comp:
|
31
|
+
comp: IComponent;
|
32
32
|
|
33
|
-
constructor(comp:
|
33
|
+
constructor(comp: IComponent) {
|
34
34
|
// console.log("CREATE NEW SYNC", comp.name, comp.guid);
|
35
35
|
this.comp = comp;
|
36
36
|
}
|
@@ -115,8 +115,7 @@
|
|
115
115
|
if (!this.comp) return;
|
116
116
|
const guid = val.guid;
|
117
117
|
if (guid && guid !== this.comp.guid) return;
|
118
|
-
if (debug)
|
119
|
-
console.log("RECEIVED", this.comp.name, this.comp.guid, val);
|
118
|
+
if (debug) console.log("RECEIVED", this.comp.name, this.comp.guid, val);
|
120
119
|
try {
|
121
120
|
this._isReceiving = true;
|
122
121
|
for (const key in val) {
|
@@ -124,6 +123,7 @@
|
|
124
123
|
// TODO: maybe use serializable here?!
|
125
124
|
const value = val[key];
|
126
125
|
this.comp[key] = value;
|
126
|
+
if(debug) console.log("SET", key, value);
|
127
127
|
}
|
128
128
|
}
|
129
129
|
catch (err) {
|
@@ -164,13 +164,16 @@
|
|
164
164
|
// }
|
165
165
|
}
|
166
166
|
else if (typeof newValue === "object" && typeof previousValue === "object") {
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
167
|
+
valueChanged = true;
|
168
|
+
// The following code doesnt work because the object is a reference type
|
169
|
+
// To properly detect changes we would have to detect assignments for each property #
|
170
|
+
// OR keep a copy of the previous object
|
171
|
+
// for (const key of Object.keys(newValue)) {
|
172
|
+
// if (newValue[key] !== previousValue[key]) {
|
173
|
+
// valueChanged = true;
|
174
|
+
// break;
|
175
|
+
// }
|
176
|
+
// }
|
174
177
|
}
|
175
178
|
}
|
176
179
|
return valueChanged;
|
@@ -203,14 +206,17 @@
|
|
203
206
|
export declare type FieldChangedCallbackFn = (newValue: any, previousValue: any) => void | boolean;
|
204
207
|
|
205
208
|
/**
|
206
|
-
* Decorate a field to be automatically networked synced
|
209
|
+
* **Decorate a field to be automatically networked synced**
|
210
|
+
* *Primitive* values are all automatically synced (like string, boolean, number).
|
211
|
+
* For *arrays or objects* make sure to re-assign them (e.g. `this.mySyncField = this.mySyncField`) to trigger an update
|
212
|
+
*
|
207
213
|
* @param onFieldChanged name of a callback function that will be called when the field is changed.
|
208
214
|
* You can also pass in a function like so: syncField(myClass.prototype.myFunctionToBeCalled)
|
209
215
|
* This function may return false to prevent notifyChanged from being called
|
210
216
|
* (for example a networked color is sent as a number and may be converted to a color in the receiver again)
|
211
217
|
* Parameters: (newValue, previousValue)
|
212
218
|
*/
|
213
|
-
export const syncField = function(onFieldChanged?: string | FieldChangedCallbackFn) {
|
219
|
+
export const syncField = function (onFieldChanged?: string | FieldChangedCallbackFn) {
|
214
220
|
|
215
221
|
return function (target: any, propertyKey: string) {
|
216
222
|
|
@@ -225,6 +231,11 @@
|
|
225
231
|
|
226
232
|
const t = target;
|
227
233
|
const internalAwake = t.__internalAwake;
|
234
|
+
if (typeof internalAwake !== "function") {
|
235
|
+
if (debug || isDevEnvironment())
|
236
|
+
console.error("@syncField can currently only used on Needle Engine Components, custom object of type \"" + target?.constructor?.name + "\" is not supported", target);
|
237
|
+
return;
|
238
|
+
}
|
228
239
|
if (debug)
|
229
240
|
console.log(propertyKey);
|
230
241
|
const backingFieldName = Symbol(propertyKey);
|
@@ -234,20 +245,35 @@
|
|
234
245
|
return;
|
235
246
|
}
|
236
247
|
this[backingFieldName] = this[propertyKey];
|
237
|
-
internalAwake.call(this);
|
238
248
|
|
239
249
|
syncer = syncerHandler.getOrCreateSyncer(this);
|
240
250
|
|
241
251
|
const desc = Object.getOwnPropertyDescriptor(this, propertyKey);
|
242
252
|
if (desc?.set === undefined) {
|
253
|
+
let invokingCallback = false;
|
243
254
|
Object.defineProperty(this, propertyKey, {
|
244
255
|
set: function (value) {
|
245
256
|
const oldValue = this[backingFieldName];
|
246
257
|
this[backingFieldName] = value;
|
247
|
-
|
248
|
-
|
249
|
-
|
258
|
+
// Prevent recursive calls when object is assigned in callback
|
259
|
+
if (invokingCallback) {
|
260
|
+
if (isDevEnvironment())
|
261
|
+
console.warn("Recursive call detected", propertyKey);
|
262
|
+
return;
|
250
263
|
}
|
264
|
+
invokingCallback = true;
|
265
|
+
try {
|
266
|
+
const valueChanged = testValueChanged(value, oldValue);
|
267
|
+
if (debug) console.log("SyncField assignment", propertyKey, "changed?", valueChanged, value);
|
268
|
+
if (valueChanged) {
|
269
|
+
if (fn?.call(this, value, oldValue) !== false) {
|
270
|
+
getSyncer(this)?.notifyChanged(propertyKey, value);
|
271
|
+
}
|
272
|
+
}
|
273
|
+
}
|
274
|
+
finally {
|
275
|
+
invokingCallback = false;
|
276
|
+
}
|
251
277
|
},
|
252
278
|
get: function () {
|
253
279
|
return this[backingFieldName];
|
@@ -258,7 +284,7 @@
|
|
258
284
|
}
|
259
285
|
|
260
286
|
syncer?.init(this);
|
261
|
-
|
287
|
+
internalAwake.call(this);
|
262
288
|
}
|
263
289
|
|
264
290
|
const internalDestroy = t.__internalDestroy;
|
@@ -281,7 +307,7 @@
|
|
281
307
|
|
282
308
|
return function <T>(target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
283
309
|
// override awake
|
284
|
-
const comp = target as
|
310
|
+
const comp = target as IComponent;
|
285
311
|
let syncer: ComponentPropertiesSyncer | null;
|
286
312
|
const internalAwake = comp.__internalAwake.bind(comp);
|
287
313
|
comp.__internalAwake = function () {
|
@@ -123,12 +123,15 @@
|
|
123
123
|
getComponentsInParent<T>(type: Constructor<T>, arr?: T[]): Array<T>;
|
124
124
|
}
|
125
125
|
|
126
|
-
export interface
|
126
|
+
export interface IHasGuid {
|
127
|
+
guid: string;
|
128
|
+
}
|
129
|
+
|
130
|
+
export interface IComponent extends IHasGuid {
|
127
131
|
get isComponent(): boolean;
|
128
132
|
|
129
|
-
|
130
133
|
gameObject: IGameObject;
|
131
|
-
guid: string;
|
134
|
+
// guid: string;
|
132
135
|
enabled: boolean;
|
133
136
|
sourceId?: SourceIdentifier;
|
134
137
|
|
@@ -604,6 +604,12 @@
|
|
604
604
|
|
605
605
|
if (!this.sessionRoot || this.sessionRoot.destroyed || !this.sessionRoot.activeAndEnabled)
|
606
606
|
this.sessionRoot = GameObject.findObjectOfType(WebARSessionRoot, context);
|
607
|
+
if (!this.sessionRoot) {
|
608
|
+
// TODO: adding it on the scene directly doesnt work (probably because then everything in the scene is disabled including this component). See code a bit furhter below where we add this component to a temporary object inside the scene
|
609
|
+
const obj = this.webxr.gameObject;
|
610
|
+
this.sessionRoot = GameObject.addNewComponent(obj, WebARSessionRoot);
|
611
|
+
console.warn("WebAR: No ARSessionRoot found, creating one automatically on the WebXR object");
|
612
|
+
}
|
607
613
|
|
608
614
|
this.previousBackground = context.scene.background;
|
609
615
|
this.previousEnvironment = context.scene.environment;
|