feat: add emergent world foundations
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
class_name WorldTargetCapability
|
||||
extends RefCounted
|
||||
|
||||
var _capability_id: StringName
|
||||
var _attributes: Dictionary
|
||||
|
||||
|
||||
func _init(capability_id: StringName = &"", attributes: Dictionary = {}) -> void:
|
||||
_capability_id = capability_id
|
||||
_attributes = attributes.duplicate(true)
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return not _capability_id.is_empty() and not _contains_object(_attributes)
|
||||
|
||||
|
||||
func get_capability_id() -> StringName:
|
||||
return _capability_id
|
||||
|
||||
|
||||
func get_attributes() -> Dictionary:
|
||||
return _attributes.duplicate(true)
|
||||
|
||||
|
||||
func get_attribute(attribute_id: StringName, default_value: Variant = null) -> Variant:
|
||||
var value: Variant = _attributes.get(String(attribute_id), default_value)
|
||||
return value.duplicate(true) if value is Array or value is Dictionary else value
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
return {
|
||||
"capability_id": String(_capability_id),
|
||||
"attributes": _attributes.duplicate(true),
|
||||
}
|
||||
|
||||
|
||||
func copy() -> WorldTargetCapability:
|
||||
return WorldTargetCapability.new(_capability_id, _attributes)
|
||||
|
||||
|
||||
static func _contains_object(value: Variant) -> bool:
|
||||
if value is Object:
|
||||
return true
|
||||
if value is Array:
|
||||
for item in value:
|
||||
if _contains_object(item):
|
||||
return true
|
||||
elif value is Dictionary:
|
||||
for key in value:
|
||||
if _contains_object(key) or _contains_object(value[key]):
|
||||
return true
|
||||
return false
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfs6efk5rokcp
|
||||
@@ -0,0 +1,126 @@
|
||||
class_name WorldTargetDescriptor
|
||||
extends RefCounted
|
||||
|
||||
var _target_id: StringName
|
||||
var _target_kind: StringName
|
||||
var _display_name: String
|
||||
var _local_position: Vector3
|
||||
var _metadata: Dictionary
|
||||
var _capabilities_by_id: Dictionary = {}
|
||||
var _capability_ids: Array[StringName] = []
|
||||
var _is_valid := false
|
||||
|
||||
|
||||
func _init(
|
||||
target_id: StringName = &"",
|
||||
target_kind: StringName = &"",
|
||||
capabilities: Array = [],
|
||||
local_position: Vector3 = Vector3.ZERO,
|
||||
display_name: String = "",
|
||||
metadata: Dictionary = {}
|
||||
) -> void:
|
||||
_target_id = target_id
|
||||
_target_kind = target_kind
|
||||
_display_name = display_name
|
||||
_local_position = local_position
|
||||
_metadata = metadata.duplicate(true)
|
||||
_is_valid = (
|
||||
not _target_id.is_empty()
|
||||
and not _target_kind.is_empty()
|
||||
and _local_position.is_finite()
|
||||
and not WorldTargetCapability._contains_object(_metadata)
|
||||
)
|
||||
for value in capabilities:
|
||||
var capability := value as WorldTargetCapability
|
||||
if (
|
||||
capability == null
|
||||
or not capability.is_valid()
|
||||
or _capabilities_by_id.has(capability.get_capability_id())
|
||||
):
|
||||
_is_valid = false
|
||||
continue
|
||||
var isolated_capability := capability.copy()
|
||||
_capabilities_by_id[isolated_capability.get_capability_id()] = isolated_capability
|
||||
_capability_ids.append(isolated_capability.get_capability_id())
|
||||
_capability_ids.sort_custom(_id_less)
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return _is_valid
|
||||
|
||||
|
||||
func get_target_id() -> StringName:
|
||||
return _target_id
|
||||
|
||||
|
||||
func get_target_kind() -> StringName:
|
||||
return _target_kind
|
||||
|
||||
|
||||
func get_display_name() -> String:
|
||||
return _display_name
|
||||
|
||||
|
||||
func get_local_position() -> Vector3:
|
||||
return _local_position
|
||||
|
||||
|
||||
func get_metadata() -> Dictionary:
|
||||
return _metadata.duplicate(true)
|
||||
|
||||
|
||||
func get_metadata_value(metadata_id: StringName, default_value: Variant = null) -> Variant:
|
||||
var value: Variant = _metadata.get(String(metadata_id), default_value)
|
||||
return value.duplicate(true) if value is Array or value is Dictionary else value
|
||||
|
||||
|
||||
func has_capability(capability_id: StringName) -> bool:
|
||||
return _capabilities_by_id.has(capability_id)
|
||||
|
||||
|
||||
func get_capability(capability_id: StringName) -> WorldTargetCapability:
|
||||
var capability := _capabilities_by_id.get(capability_id) as WorldTargetCapability
|
||||
return capability.copy() if capability != null else null
|
||||
|
||||
|
||||
func get_capability_ids() -> Array[StringName]:
|
||||
return _capability_ids.duplicate()
|
||||
|
||||
|
||||
func get_capabilities() -> Array[WorldTargetCapability]:
|
||||
var capabilities: Array[WorldTargetCapability] = []
|
||||
for capability_id in _capability_ids:
|
||||
capabilities.append((_capabilities_by_id[capability_id] as WorldTargetCapability).copy())
|
||||
return capabilities
|
||||
|
||||
|
||||
func copy() -> WorldTargetDescriptor:
|
||||
return WorldTargetDescriptor.new(
|
||||
_target_id, _target_kind, get_capabilities(), _local_position, _display_name, _metadata
|
||||
)
|
||||
|
||||
|
||||
func with_local_position(next_position: Vector3) -> WorldTargetDescriptor:
|
||||
return WorldTargetDescriptor.new(
|
||||
_target_id, _target_kind, get_capabilities(), next_position, _display_name, _metadata
|
||||
)
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
var capabilities: Array[Dictionary] = []
|
||||
for capability_id in _capability_ids:
|
||||
capabilities.append(
|
||||
(_capabilities_by_id[capability_id] as WorldTargetCapability).to_dictionary()
|
||||
)
|
||||
return {
|
||||
"target_id": String(_target_id),
|
||||
"target_kind": String(_target_kind),
|
||||
"display_name": _display_name,
|
||||
"local_position": [_local_position.x, _local_position.y, _local_position.z],
|
||||
"metadata": _metadata.duplicate(true),
|
||||
"capabilities": capabilities,
|
||||
}
|
||||
|
||||
|
||||
static func _id_less(first: StringName, second: StringName) -> bool:
|
||||
return String(first) < String(second)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bjvptxki1v7q0
|
||||
@@ -0,0 +1,52 @@
|
||||
class_name WorldTargetHandle
|
||||
extends RefCounted
|
||||
|
||||
var _registry_instance_id: int
|
||||
var _context_id: StringName
|
||||
var _target_id: StringName
|
||||
var _target_kind: StringName
|
||||
var _generation: int
|
||||
|
||||
|
||||
func _init(
|
||||
registry_instance_id: int = 0,
|
||||
context_id: StringName = &"",
|
||||
target_id: StringName = &"",
|
||||
target_kind: StringName = &"",
|
||||
generation: int = 0
|
||||
) -> void:
|
||||
_registry_instance_id = registry_instance_id
|
||||
_context_id = context_id
|
||||
_target_id = target_id
|
||||
_target_kind = target_kind
|
||||
_generation = generation
|
||||
|
||||
|
||||
func is_well_formed() -> bool:
|
||||
return (
|
||||
_registry_instance_id != 0
|
||||
and not _context_id.is_empty()
|
||||
and not _target_id.is_empty()
|
||||
and not _target_kind.is_empty()
|
||||
and _generation > 0
|
||||
)
|
||||
|
||||
|
||||
func get_registry_instance_id() -> int:
|
||||
return _registry_instance_id
|
||||
|
||||
|
||||
func get_context_id() -> StringName:
|
||||
return _context_id
|
||||
|
||||
|
||||
func get_target_id() -> StringName:
|
||||
return _target_id
|
||||
|
||||
|
||||
func get_target_kind() -> StringName:
|
||||
return _target_kind
|
||||
|
||||
|
||||
func get_generation() -> int:
|
||||
return _generation
|
||||
@@ -0,0 +1 @@
|
||||
uid://dcun0sd0f8ext
|
||||
@@ -0,0 +1,263 @@
|
||||
class_name WorldTargetRegistry
|
||||
extends RefCounted
|
||||
|
||||
var last_error := ""
|
||||
|
||||
var _context_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 = &"") -> void:
|
||||
_context_id = owned_context_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 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 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
|
||||
@@ -0,0 +1 @@
|
||||
uid://b6ok1rowe60rg
|
||||
Reference in New Issue
Block a user