124 lines
3.6 KiB
JavaScript
124 lines
3.6 KiB
JavaScript
|
|
import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js";
|
||
|
|
import { guidGenerator } from "../core/util.js";
|
||
|
|
let instanceCounter = 0;
|
||
|
|
|
||
|
|
export class Instance {
|
||
|
|
constructor(className = "Instance") {
|
||
|
|
this.ClassName = className;
|
||
|
|
this.Name = `${className}${instanceCounter++}`;
|
||
|
|
this.Parent = null;
|
||
|
|
this.Children = [];
|
||
|
|
this.InstanceId = guidGenerator(); // Unique identifier, used for replication
|
||
|
|
|
||
|
|
// Signals
|
||
|
|
this.AncestryChanged = new BO3ScriptSignal();
|
||
|
|
this.Changed = new BO3ScriptSignal();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set a property dynamically
|
||
|
|
SetProperty(prop, value) {
|
||
|
|
this[prop] = value;
|
||
|
|
this.Changed.Fire(prop, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
GetDataModel() { // Recursively calls itself on parent until DataModel is found, since DataModel overrides this method to return itself
|
||
|
|
if (this.Parent) return this.Parent.GetDataModel();
|
||
|
|
}
|
||
|
|
|
||
|
|
FindInstanceRecursively(instanceId) {
|
||
|
|
if (this.InstanceId === instanceId) return this;
|
||
|
|
if (this.Children) {
|
||
|
|
for (const child of this.Children) {
|
||
|
|
if (typeof child.FindInstanceRecursively === 'function') {
|
||
|
|
const found = child.FindInstanceRecursively(instanceId);
|
||
|
|
if (found) return found;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
OnReplicated() {
|
||
|
|
// Hook for when instance is replicated to client. Override in subclasses.
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parent management
|
||
|
|
SetParent(newParent) {
|
||
|
|
if (this.Parent === newParent) return;
|
||
|
|
|
||
|
|
// Remove from old parent
|
||
|
|
if (this.Parent) {
|
||
|
|
const oldChildren = this.Parent.Children;
|
||
|
|
this.Parent.Children = oldChildren.filter(c => c !== this);
|
||
|
|
}
|
||
|
|
|
||
|
|
const oldParent = this.Parent;
|
||
|
|
this.Parent = newParent;
|
||
|
|
|
||
|
|
// Add to new parent
|
||
|
|
if (newParent) {
|
||
|
|
newParent.Children.push(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
this.AncestryChanged.Fire(this, oldParent);
|
||
|
|
} catch {
|
||
|
|
console.warn("AncestryChanged signal error! May be caused by ReplicatorService during replication.");
|
||
|
|
}
|
||
|
|
if (oldParent === null && newParent !== null && this.OnAddedToScene) {
|
||
|
|
// If added to scene (i.e. RenderService), call hook
|
||
|
|
setTimeout(() => {
|
||
|
|
this.OnAddedToScene(newParent);
|
||
|
|
}, 0); // Defer to avoid issues during replication
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Utility: find first child with name
|
||
|
|
FindFirstChild(name) {
|
||
|
|
return this.Children.find(c => c.Name === name) || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Utility: create deep clone
|
||
|
|
Clone() {
|
||
|
|
const clone = new Instance(this.ClassName);
|
||
|
|
for (const key of Object.keys(this)) {
|
||
|
|
if (key !== "Parent" && key !== "Children") {
|
||
|
|
clone[key] = structuredClone(this[key]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const child of this.Children) {
|
||
|
|
const childClone = child.Clone();
|
||
|
|
childClone.SetParent(clone);
|
||
|
|
}
|
||
|
|
return clone;
|
||
|
|
}
|
||
|
|
|
||
|
|
Destroy() {
|
||
|
|
// Remove from parent
|
||
|
|
const dm = this.GetDataModel(); // Get DataModel for replication service, BEFORE removing parent. otherwise can't find it.
|
||
|
|
this.SetParent(null);
|
||
|
|
// Recursively destroy children
|
||
|
|
for (const child of [...this.Children]) {
|
||
|
|
if (typeof child.Destroy === 'function') {
|
||
|
|
child.Destroy();
|
||
|
|
} else {
|
||
|
|
child.SetParent(null);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Clear signals
|
||
|
|
this.AncestryChanged.DisconnectAll();
|
||
|
|
this.Changed.DisconnectAll();
|
||
|
|
// Request replicator to remove this instance if needed
|
||
|
|
if (dm) {
|
||
|
|
const replicator = dm.GetService("ReplicatorService");
|
||
|
|
if (replicator) {
|
||
|
|
replicator.requestReplicateDestroy(this);
|
||
|
|
} else {
|
||
|
|
console.warn("Destroy: No ReplicatorService found in DataModel.");
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.warn("Destroy: No DataModel found in ancestry. Are you destroying an unparented instance?");
|
||
|
|
}
|
||
|
|
} // Garbage collected after this
|
||
|
|
}
|