class_name WorldTargetRegistry extends RefCounted var last_error := "" var _context_id: StringName var _world_id: StringName var _location_id: StringName var _entries_by_id: Dictionary = {} var _generation_by_id: Dictionary = {} var _sorted_target_ids: Array[StringName] = [] var _target_ids_by_kind: Dictionary = {} var _target_ids_by_capability: Dictionary = {} func _init( owned_context_id: StringName = &"", owned_world_id: StringName = &"", owned_location_id: StringName = &"" ) -> void: _context_id = owned_context_id _world_id = owned_world_id _location_id = owned_location_id func register_target( descriptor: WorldTargetDescriptor, provider: Object = null ) -> WorldTargetHandle: last_error = "" if _context_id.is_empty(): return _fail_handle("Registry context_id must not be empty") if descriptor == null or not descriptor.is_valid(): return _fail_handle("Target descriptor is invalid") if provider != null and not is_instance_valid(provider): return _fail_handle("Target provider is invalid") var target_id := descriptor.get_target_id() if _entries_by_id.has(target_id): var existing := _entries_by_id[target_id]["descriptor"] as WorldTargetDescriptor return _fail_handle( ( "Target ID '%s' is already registered as kind '%s'" % [target_id, existing.get_target_kind()] ) ) var isolated_descriptor := descriptor.copy() var generation := int(_generation_by_id.get(target_id, 0)) + 1 _generation_by_id[target_id] = generation _entries_by_id[target_id] = { "descriptor": isolated_descriptor, "generation": generation, "has_provider": provider != null, "provider_ref": weakref(provider) if provider != null else null, } _insert_sorted_unique(_sorted_target_ids, target_id) _add_to_index(_target_ids_by_kind, isolated_descriptor.get_target_kind(), target_id) for capability_id in isolated_descriptor.get_capability_ids(): _add_to_index(_target_ids_by_capability, capability_id, target_id) return _make_handle(isolated_descriptor, generation) func update_target(handle: WorldTargetHandle, descriptor: WorldTargetDescriptor) -> bool: last_error = "" var entry := _get_handle_entry(handle) if entry.is_empty(): return _fail_bool("Target handle is stale or belongs to another registry") if descriptor == null or not descriptor.is_valid(): return _fail_bool("Target descriptor is invalid") var previous := entry["descriptor"] as WorldTargetDescriptor if ( descriptor.get_target_id() != previous.get_target_id() or descriptor.get_target_kind() != previous.get_target_kind() ): return _fail_bool("Target identity and kind cannot change during an update") for capability_id in previous.get_capability_ids(): _remove_from_index(_target_ids_by_capability, capability_id, previous.get_target_id()) var isolated_descriptor := descriptor.copy() entry["descriptor"] = isolated_descriptor _entries_by_id[isolated_descriptor.get_target_id()] = entry for capability_id in isolated_descriptor.get_capability_ids(): _add_to_index(_target_ids_by_capability, capability_id, isolated_descriptor.get_target_id()) return true func update_target_position(handle: WorldTargetHandle, local_position: Vector3) -> bool: last_error = "" var entry := _get_handle_entry(handle) if entry.is_empty(): return _fail_bool("Target handle is stale or belongs to another registry") if not local_position.is_finite(): return _fail_bool("Target position must be finite") var previous := entry["descriptor"] as WorldTargetDescriptor entry["descriptor"] = previous.with_local_position(local_position) _entries_by_id[previous.get_target_id()] = entry return true func unregister_target(handle: WorldTargetHandle) -> bool: last_error = "" var entry := _get_handle_entry(handle) if entry.is_empty(): return _fail_bool("Target handle is stale or belongs to another registry") _erase_entry(handle.get_target_id(), entry) return true func clear() -> void: for target_id in _sorted_target_ids.duplicate(): _erase_entry(target_id, _entries_by_id[target_id]) last_error = "" func prune_invalid_providers() -> Array[StringName]: var removed_ids: Array[StringName] = [] for target_id in _sorted_target_ids.duplicate(): var entry: Dictionary = _entries_by_id[target_id] if not bool(entry["has_provider"]): continue var provider_ref := entry["provider_ref"] as WeakRef if provider_ref != null and provider_ref.get_ref() != null: continue _erase_entry(target_id, entry) removed_ids.append(target_id) return removed_ids func has_target(target_id: StringName) -> bool: return _entries_by_id.has(target_id) func get_context_id() -> StringName: return _context_id func get_world_id() -> StringName: return _world_id func get_location_id() -> StringName: return _location_id func get_identity() -> Dictionary: return { "context_id": _context_id, "world_id": _world_id, "location_id": _location_id, } func size() -> int: return _entries_by_id.size() func get_descriptor(target_id: StringName) -> WorldTargetDescriptor: var entry: Dictionary = _entries_by_id.get(target_id, {}) return entry.get("descriptor") as WorldTargetDescriptor func get_handle(target_id: StringName) -> WorldTargetHandle: var entry: Dictionary = _entries_by_id.get(target_id, {}) if entry.is_empty(): return null return _make_handle(entry["descriptor"] as WorldTargetDescriptor, int(entry["generation"])) func resolve_handle(handle: WorldTargetHandle) -> WorldTargetDescriptor: var entry := _get_handle_entry(handle) return entry.get("descriptor") as WorldTargetDescriptor func is_handle_valid(handle: WorldTargetHandle, require_provider: bool = false) -> bool: var entry := _get_handle_entry(handle) if entry.is_empty(): return false if not require_provider: return true if not bool(entry["has_provider"]): return false var provider_ref := entry["provider_ref"] as WeakRef return provider_ref != null and provider_ref.get_ref() != null func get_provider(handle: WorldTargetHandle) -> Object: var entry := _get_handle_entry(handle) if entry.is_empty() or not bool(entry["has_provider"]): return null var provider_ref := entry["provider_ref"] as WeakRef return provider_ref.get_ref() if provider_ref != null else null func get_target_ids( target_kind: StringName = &"", capability_id: StringName = &"" ) -> Array[StringName]: var source_ids: Array = _sorted_target_ids if not capability_id.is_empty(): source_ids = _target_ids_by_capability.get(capability_id, []) elif not target_kind.is_empty(): source_ids = _target_ids_by_kind.get(target_kind, []) var result: Array[StringName] = [] for target_id in source_ids: var descriptor := get_descriptor(target_id) if descriptor == null: continue if not target_kind.is_empty() and descriptor.get_target_kind() != target_kind: continue if not capability_id.is_empty() and not descriptor.has_capability(capability_id): continue result.append(target_id) return result func get_descriptors( target_kind: StringName = &"", capability_id: StringName = &"" ) -> Array[WorldTargetDescriptor]: var descriptors: Array[WorldTargetDescriptor] = [] for target_id in get_target_ids(target_kind, capability_id): descriptors.append(get_descriptor(target_id)) return descriptors func _get_handle_entry(handle: WorldTargetHandle) -> Dictionary: if ( handle == null or not handle.is_well_formed() or handle.get_registry_instance_id() != get_instance_id() or handle.get_context_id() != _context_id ): return {} var entry: Dictionary = _entries_by_id.get(handle.get_target_id(), {}) if entry.is_empty(): return {} var descriptor := entry["descriptor"] as WorldTargetDescriptor if ( int(entry["generation"]) != handle.get_generation() or descriptor.get_target_kind() != handle.get_target_kind() ): return {} return entry func _make_handle(descriptor: WorldTargetDescriptor, generation: int) -> WorldTargetHandle: return WorldTargetHandle.new( get_instance_id(), _context_id, descriptor.get_target_id(), descriptor.get_target_kind(), generation ) func _erase_entry(target_id: StringName, entry: Dictionary) -> void: var descriptor := entry["descriptor"] as WorldTargetDescriptor _sorted_target_ids.erase(target_id) _remove_from_index(_target_ids_by_kind, descriptor.get_target_kind(), target_id) for capability_id in descriptor.get_capability_ids(): _remove_from_index(_target_ids_by_capability, capability_id, target_id) _entries_by_id.erase(target_id) func _add_to_index(index: Dictionary, index_id: StringName, target_id: StringName) -> void: var target_ids: Array = index.get(index_id, []) _insert_sorted_unique(target_ids, target_id) index[index_id] = target_ids func _remove_from_index(index: Dictionary, index_id: StringName, target_id: StringName) -> void: var target_ids: Array = index.get(index_id, []) target_ids.erase(target_id) if target_ids.is_empty(): index.erase(index_id) else: index[index_id] = target_ids func _insert_sorted_unique(target_ids: Array, target_id: StringName) -> void: var low := 0 var high := target_ids.size() var target_text := String(target_id) while low < high: var middle := (low + high) / 2 var middle_text := String(target_ids[middle]) if middle_text < target_text: low = middle + 1 else: high = middle if low < target_ids.size() and String(target_ids[low]) == target_text: return target_ids.insert(low, target_id) func _fail_handle(message: String) -> WorldTargetHandle: last_error = message return null func _fail_bool(message: String) -> bool: last_error = message return false