extends RefCounted class_name _Snapshot # Stores property values of multiple subjects, recorded for a specific tick var tick: int var _data := {} # object to (property to variant) var _auth_subjects := _Set.new() static func make_patch(from: _Snapshot, to: _Snapshot, tick: int = to.tick) -> _Snapshot: var patch := _Snapshot.new(tick) for subject in from._data: # Target has no knowledge of subject, don't patch if not to._data.has(subject): continue # Only patch to auth subjects if not to.is_auth(subject): continue for property in to._data[subject]: # Target snapshot has different value, patch it if from.get_property(subject, property) != to.get_property(subject, property): patch.set_property(subject, property, to.get_property(subject, property)) patch.set_auth(subject, to.is_auth(subject)) return patch # Each entry should be [subject, property, value] static func of(tick: int, entries: Array[Array], auth_subjects: Array[Object]) -> _Snapshot: var snapshot := _Snapshot.new(tick) for entry in entries: var subject := entry[0] as Object var property := entry[1] as NodePath var value := entry[2] as Variant snapshot.set_property(subject, property, value) for subject in auth_subjects: snapshot.set_auth(subject, true) return snapshot func _init(p_tick: int): tick = p_tick func duplicate() -> _Snapshot: var result := _Snapshot.new(tick) result._data = _data.duplicate(true) result._auth_subjects = _auth_subjects.duplicate() return result func set_auth(subject: Object, is_auth: bool) -> void: if is_auth: _auth_subjects.add(subject) else: _auth_subjects.erase(subject) func set_property(subject: Object, property: NodePath, value: Variant) -> void: if not _data.has(subject): _data[subject] = { property: value } else: _data[subject][property] = value func record_property(subject: Object, property: NodePath) -> void: var value := subject.get_indexed(property) set_property(subject, property, value) func get_property(subject: Object, property: NodePath) -> Variant: return _data.get(subject, {}).get(property) func has_property(subject: Object, property: NodePath) -> bool: if not _data.has(subject): return false if not _data[subject].has(property): return false return true func merge(snapshot: _Snapshot) -> bool: var has_changed := false for subject in snapshot._data: if not _data.has(subject): # We have no data of the subject, copy all _data[subject] = snapshot._data[subject].duplicate() set_auth(subject, snapshot.is_auth(subject)) has_changed = true continue if snapshot.is_auth(subject) or not is_auth(subject): var own_props := _data[subject] as Dictionary var their_props := snapshot._data[subject] as Dictionary has_changed = has_changed or own_props != their_props own_props.merge(their_props, true) set_auth(subject, snapshot.is_auth(subject)) return has_changed func apply() -> void: for subject in _data: for property in _data[subject]: var value = _data[subject][property] (subject as Object).set_indexed(property, value) func sanitize(sender: int) -> void: var invalid_subjects := [] for subject in _data: if subject is Node: if subject.get_multiplayer_authority() != sender: invalid_subjects.append(subject) for subject in invalid_subjects: _data.erase(invalid_subjects) func has_subject(subject: Object, require_auth: bool = false) -> bool: if not _data.has(subject): return false if require_auth and not is_auth(subject): return false return true func has_subjects(subjects: Array, require_auth: bool = false) -> bool: for subject in subjects: if not has_subject(subject, require_auth): return false return true func get_subjects() -> Array: return _data.keys() func get_auth_subjects() -> Array: return _auth_subjects.values() func get_subject_properties(subject: Object) -> Array: return _data.get(subject, {}).keys() func is_empty() -> bool: return _data.is_empty() func size() -> int: var result := 0 for subject in _data: result += (_data[subject] as Dictionary).size() return result func is_auth(subject: Object) -> bool: return _auth_subjects.has(subject) func equals(other) -> bool: if other is _Snapshot: return tick == other.tick and _data == other._data and _auth_subjects.equals(other._auth_subjects) else: return false func _to_string() -> String: var result := "_Snapshot(#%d" % [tick] for subject in _data: for property in _data[subject]: var value = _data[subject][property] result += ", %s:%s(%s): %s" % [subject, property, is_auth(subject), value] result += ")" return result func _to_vest(): return { "tick": tick, "data": _data, "auth_subjects": _auth_subjects }