Files
TheTree_mock/scripts/world/spatial_subslot_service.gd

891 lines
32 KiB
GDScript

class_name SpatialSubslotService
extends RefCounted
## Independent spatial occupancy for systems that may share a WorldService cell
## with a primary facility. Every layer cell owns exactly one slot per category.
signal entity_placed(entity_id: StringName, category: StringName, cells: Array[Vector3i])
signal entity_removed(entity_id: StringName, category: StringName, cells: Array[Vector3i])
signal entity_cells_replaced(
entity_id: StringName,
category: StringName,
before_cells: Array[Vector3i],
after_cells: Array[Vector3i]
)
const CommandResultType = preload("res://scripts/core/command_result.gd")
const SNAPSHOT_SCHEMA_VERSION := 1
const CATEGORY_ROUTE: StringName = &"ROUTE"
const CATEGORY_DECORATION: StringName = &"DECORATION"
const CATEGORY_UTILITY: StringName = &"UTILITY"
const CATEGORIES: Array[StringName] = [
CATEGORY_ROUTE,
CATEGORY_DECORATION,
CATEGORY_UTILITY,
]
const LAYER_FRONT := 0
const LAYER_BACK := 1
const MAX_ENTITY_ID_LENGTH := 128
const MAX_ENTITIES := 4_096
const MAX_CELLS_PER_ENTITY := 256
const MAX_TOTAL_SLOTS := 65_536
const MAX_DIRTY_CELLS := 65_536
const MAX_COORDINATE_ABS := 100_000
const MAX_REVISION := 9_007_199_254_740_991
const MAX_SNAPSHOT_DEPTH := 8
const MAX_CONTAINER_ITEMS := 65_536
var revision := 0
var last_error := ""
var last_callback_report: Dictionary = {}
var _category_revisions: Dictionary = {
CATEGORY_ROUTE: 0,
CATEGORY_DECORATION: 0,
CATEGORY_UTILITY: 0,
}
var _slots: Dictionary = {}
var _entity_cells: Dictionary = {}
var _entity_categories: Dictionary = {}
var _dirty_cells: Dictionary = {}
var _compatibility_validator := Callable()
var _mutation_active := false
## Optional read-only boundary for World-specific rules such as pass-through
## facilities. The callback receives one defensive payload and must return a
## strict `{ok: bool, reason?: StringName, details?: Dictionary}` response.
func configure_compatibility_validator(callback: Callable = Callable()) -> bool:
if _mutation_active:
last_error = "Spatial subslot mutation is already active."
return false
_compatibility_validator = callback
last_callback_report = {}
last_error = ""
return true
func place(entity_id: Variant, category: Variant, cells: Variant) -> CommandResult:
if _mutation_active:
return _failure(CommandResultType.Code.TRANSACTION_CLOSED, "Spatial subslot mutation is already active.")
_mutation_active = true
var result := _place_internal(entity_id, category, cells)
_mutation_active = false
return result
func remove(entity_id: Variant) -> CommandResult:
if _mutation_active:
return _failure(CommandResultType.Code.TRANSACTION_CLOSED, "Spatial subslot mutation is already active.")
_mutation_active = true
var result := _remove_internal(entity_id)
_mutation_active = false
return result
func replace_entity_cells(entity_id: Variant, cells: Variant) -> CommandResult:
if _mutation_active:
return _failure(CommandResultType.Code.TRANSACTION_CLOSED, "Spatial subslot mutation is already active.")
_mutation_active = true
var result := _replace_internal(entity_id, cells)
_mutation_active = false
return result
func has_entity(entity_id: StringName) -> bool:
return _entity_cells.has(entity_id)
func get_entity_category(entity_id: StringName) -> StringName:
return StringName(_entity_categories.get(entity_id, &""))
func get_entity_cells(entity_id: StringName) -> Array[Vector3i]:
var source: Variant = _entity_cells.get(entity_id, [])
return _copy_cells(source) if source is Array else []
func get_entity_ids(category: Variant = null) -> Array[StringName]:
var filter_category: StringName = &""
if category != null:
if not _category_is_valid(category):
return []
filter_category = StringName(category)
var result: Array[StringName] = []
for entity_id: StringName in _sorted_entity_ids(_entity_cells):
if filter_category.is_empty() or _entity_categories[entity_id] == filter_category:
result.append(entity_id)
return result
func get_entities(category: Variant = null) -> Array[Dictionary]:
var result: Array[Dictionary] = []
for entity_id: StringName in get_entity_ids(category):
result.append({
&"entity_id": entity_id,
&"category": _entity_categories[entity_id],
&"cells": get_entity_cells(entity_id),
})
return result
func get_slot_entity(cell: Variant, category: Variant) -> StringName:
if not _cell_is_valid(cell) or not _category_is_valid(category):
return &""
var cell_slots: Variant = _slots.get(cell, {})
if not cell_slots is Dictionary:
return &""
return StringName(cell_slots.get(StringName(category), &""))
func get_cell_slots(cell: Variant) -> Dictionary:
if not _cell_is_valid(cell):
return {}
var source: Variant = _slots.get(cell, {})
if not source is Dictionary:
return {}
var result: Dictionary = {}
for category: StringName in CATEGORIES:
if source.has(category):
result[category] = StringName(source[category])
return result
func get_cells_for_category(category: Variant) -> Array[Vector3i]:
if not _category_is_valid(category):
return []
var cell_set: Dictionary = {}
var canonical_category := StringName(category)
for entity_id: StringName in _sorted_entity_ids(_entity_cells):
if _entity_categories[entity_id] != canonical_category:
continue
for cell: Vector3i in _entity_cells[entity_id]:
cell_set[cell] = true
return _sorted_cells(cell_set.keys())
## Canonical passable-cell input for a future PathService graph rebuild.
func get_route_cells() -> Array[Vector3i]:
return get_cells_for_category(CATEGORY_ROUTE)
func get_revision_for_category(category: Variant) -> int:
if not _category_is_valid(category):
return -1
return int(_category_revisions[StringName(category)])
func get_dirty_revision() -> int:
return revision
func get_dirty_cells() -> Array[Vector3i]:
return _sorted_cells(_dirty_cells.keys())
func consume_dirty_cells() -> Array[Vector3i]:
if _mutation_active:
last_error = "Dirty cells cannot be consumed during a spatial mutation."
return []
var result := get_dirty_cells()
_dirty_cells.clear()
last_error = ""
return result
func get_last_callback_report() -> Dictionary:
return last_callback_report.duplicate(true)
func create_snapshot() -> Dictionary:
var entities: Array[Dictionary] = []
for entity_id: StringName in _sorted_entity_ids(_entity_cells):
entities.append({
&"entity_id": String(entity_id),
&"category": String(_entity_categories[entity_id]),
&"cells": get_entity_cells(entity_id),
})
return {
&"schema_version": SNAPSHOT_SCHEMA_VERSION,
&"revision": revision,
&"category_revisions": {
&"ROUTE": int(_category_revisions[CATEGORY_ROUTE]),
&"DECORATION": int(_category_revisions[CATEGORY_DECORATION]),
&"UTILITY": int(_category_revisions[CATEGORY_UTILITY]),
},
&"dirty_cells": get_dirty_cells(),
&"entities": entities,
}
func restore_snapshot(snapshot: Dictionary) -> bool:
if _mutation_active:
last_error = "Spatial subslot mutation is already active."
return false
_mutation_active = true
var parsed := _parse_snapshot(snapshot)
if not bool(parsed.get(&"ok", false)):
_mutation_active = false
last_error = String(parsed.get(&"message", "Spatial subslot snapshot is invalid."))
return false
_slots = parsed[&"slots"]
_entity_cells = parsed[&"entity_cells"]
_entity_categories = parsed[&"entity_categories"]
_dirty_cells = parsed[&"dirty_cells"]
_category_revisions = parsed[&"category_revisions"]
revision = int(parsed[&"revision"])
_mutation_active = false
last_callback_report = {}
last_error = ""
return true
func validate_invariants() -> Dictionary:
var errors: Array[String] = []
if revision < 0 or revision > MAX_REVISION:
errors.append("Revision is outside the supported range.")
if not _dictionary_has_exact_keys(_category_revisions, CATEGORIES):
errors.append("Category revision keys are not canonical.")
else:
var category_revisions_valid := true
var latest_category_revision := 0
for category: StringName in CATEGORIES:
var raw_category_revision: Variant = _category_revisions[category]
if typeof(raw_category_revision) != TYPE_INT \
or int(raw_category_revision) < 0 \
or int(raw_category_revision) > revision:
errors.append("Category revision is invalid: %s" % category)
category_revisions_valid = false
else:
latest_category_revision = max(latest_category_revision, int(raw_category_revision))
if category_revisions_valid and revision > 0 and latest_category_revision != revision:
errors.append("No category revision matches the global revision.")
if _entity_cells.size() > MAX_ENTITIES or _slots.size() > MAX_TOTAL_SLOTS:
errors.append("Spatial subslot capacity is exceeded.")
if _entity_cells.size() != _entity_categories.size():
errors.append("Entity cell/category maps differ in size.")
var expected_slots: Dictionary = {}
var total_slots := 0
var valid_entity_ids: Array[StringName] = []
for raw_entity_id: Variant in _entity_cells:
if typeof(raw_entity_id) not in [TYPE_STRING, TYPE_STRING_NAME] \
or not _stable_id_is_valid(raw_entity_id):
errors.append("Entity cell map contains an invalid ID.")
continue
valid_entity_ids.append(StringName(raw_entity_id))
valid_entity_ids.sort_custom(_name_less)
for entity_id: StringName in valid_entity_ids:
if not _stable_id_is_valid(entity_id) or not _entity_categories.has(entity_id):
errors.append("Entity ID/category crosslink is invalid: %s" % entity_id)
continue
var category: Variant = _entity_categories[entity_id]
var raw_cells: Variant = _entity_cells[entity_id]
if not _category_is_valid(category) or not raw_cells is Array:
errors.append("Entity category/cells are invalid: %s" % entity_id)
continue
var validation := _validate_cells(raw_cells)
if not bool(validation[&"ok"]) or validation[&"cells"] != raw_cells:
errors.append("Entity cells are invalid or unordered: %s" % entity_id)
continue
for cell: Vector3i in validation[&"cells"]:
var cell_slots: Dictionary = expected_slots.get(cell, {})
if cell_slots.has(category):
errors.append("Two entities claim one category slot at %s." % cell)
else:
cell_slots[category] = entity_id
expected_slots[cell] = cell_slots
total_slots += 1
if total_slots > MAX_TOTAL_SLOTS:
errors.append("Total occupied subslots exceed the capacity.")
if expected_slots != _slots:
errors.append("Cell/entity/category crosslinks do not match.")
if _dirty_cells.size() > MAX_DIRTY_CELLS:
errors.append("Dirty-cell capacity is exceeded.")
for raw_cell: Variant in _dirty_cells:
if not _cell_is_valid(raw_cell) \
or typeof(_dirty_cells[raw_cell]) != TYPE_BOOL \
or not bool(_dirty_cells[raw_cell]):
errors.append("Dirty cell is invalid.")
return {&"ok": errors.is_empty(), &"errors": errors}
func _place_internal(entity_id_value: Variant, category_value: Variant, cells_value: Variant) -> CommandResult:
var identity := _parse_identity(entity_id_value, category_value)
if not bool(identity[&"ok"]):
return _failure(CommandResultType.Code.INVALID_PAYLOAD, String(identity[&"message"]))
var entity_id: StringName = identity[&"entity_id"]
var category: StringName = identity[&"category"]
if _entity_cells.has(entity_id):
return _failure(CommandResultType.Code.DUPLICATE_COMMAND_ID, "Spatial entity ID already exists.")
if _entity_cells.size() >= MAX_ENTITIES:
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Spatial entity capacity is exhausted.")
var cell_validation := _validate_cells(cells_value)
if not bool(cell_validation[&"ok"]):
return _failure(CommandResultType.Code.INVALID_PAYLOAD, String(cell_validation[&"message"]))
var cells: Array[Vector3i] = cell_validation[&"cells"]
var current_total := _total_occupied_slots()
if current_total > MAX_TOTAL_SLOTS - cells.size():
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Spatial subslot capacity is exhausted.")
for cell: Vector3i in cells:
var cell_slots: Dictionary = _slots.get(cell, {})
if cell_slots.has(category):
return _failure(
CommandResultType.Code.VALIDATION_FAILED,
"Spatial category slot is already occupied.",
{&"cell": cell, &"category": category, &"occupant_id": cell_slots[category]}
)
if not _dirty_capacity_allows(cells):
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Dirty-cell capacity is exhausted.")
if revision >= MAX_REVISION:
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Spatial revision limit is exhausted.")
var payload := _mutation_payload(&"PLACE", entity_id, category, [], cells)
var boundary := _validate_external_boundary(payload)
if not bool(boundary[&"ok"]):
return _failure(
CommandResultType.Code.TRANSACTION_APPLY_FAILED,
String(boundary[&"message"]),
boundary.get(&"details", {})
)
for cell: Vector3i in cells:
var cell_slots: Dictionary = _slots.get(cell, {})
cell_slots[category] = entity_id
_slots[cell] = cell_slots
_entity_cells[entity_id] = cells.duplicate()
_entity_categories[entity_id] = category
_mark_changed(category, cells)
entity_placed.emit(entity_id, category, cells.duplicate())
last_error = ""
return _success({
&"entity_id": entity_id,
&"category": category,
&"cells": cells.duplicate(),
&"revision": revision,
&"changed": true,
})
func _remove_internal(entity_id_value: Variant) -> CommandResult:
if not _stable_id_is_valid(entity_id_value):
return _failure(CommandResultType.Code.INVALID_PAYLOAD, "Spatial entity ID is invalid.")
var entity_id := StringName(entity_id_value)
if not _entity_cells.has(entity_id):
return _failure(CommandResultType.Code.NOT_FOUND, "Spatial entity was not found.")
var category: StringName = _entity_categories[entity_id]
var cells := get_entity_cells(entity_id)
if not _dirty_capacity_allows(cells):
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Dirty-cell capacity is exhausted.")
if revision >= MAX_REVISION:
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Spatial revision limit is exhausted.")
var boundary := _validate_external_boundary(
_mutation_payload(&"REMOVE", entity_id, category, cells, [])
)
if not bool(boundary[&"ok"]):
return _failure(
CommandResultType.Code.TRANSACTION_APPLY_FAILED,
String(boundary[&"message"]),
boundary.get(&"details", {})
)
for cell: Vector3i in cells:
var cell_slots: Dictionary = _slots.get(cell, {})
if cell_slots.get(category, &"") == entity_id:
cell_slots.erase(category)
if cell_slots.is_empty():
_slots.erase(cell)
else:
_slots[cell] = cell_slots
_entity_cells.erase(entity_id)
_entity_categories.erase(entity_id)
_mark_changed(category, cells)
entity_removed.emit(entity_id, category, cells.duplicate())
last_error = ""
return _success({
&"entity_id": entity_id,
&"category": category,
&"cells": cells,
&"revision": revision,
&"changed": true,
})
func _replace_internal(entity_id_value: Variant, cells_value: Variant) -> CommandResult:
if not _stable_id_is_valid(entity_id_value):
return _failure(CommandResultType.Code.INVALID_PAYLOAD, "Spatial entity ID is invalid.")
var entity_id := StringName(entity_id_value)
if not _entity_cells.has(entity_id):
return _failure(CommandResultType.Code.NOT_FOUND, "Spatial entity was not found.")
var validation := _validate_cells(cells_value)
if not bool(validation[&"ok"]):
return _failure(CommandResultType.Code.INVALID_PAYLOAD, String(validation[&"message"]))
var after_cells: Array[Vector3i] = validation[&"cells"]
var before_cells := get_entity_cells(entity_id)
var category: StringName = _entity_categories[entity_id]
if before_cells == after_cells:
last_error = ""
return _success({
&"entity_id": entity_id,
&"category": category,
&"cells": after_cells.duplicate(),
&"revision": revision,
&"changed": false,
})
var projected_total := _total_occupied_slots() - before_cells.size() + after_cells.size()
if projected_total > MAX_TOTAL_SLOTS:
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Spatial subslot capacity is exhausted.")
for cell: Vector3i in after_cells:
var occupant := get_slot_entity(cell, category)
if not occupant.is_empty() and occupant != entity_id:
return _failure(
CommandResultType.Code.VALIDATION_FAILED,
"Spatial category slot is already occupied.",
{&"cell": cell, &"category": category, &"occupant_id": occupant}
)
var dirty_union: Dictionary = {}
for cell: Vector3i in before_cells:
dirty_union[cell] = true
for cell: Vector3i in after_cells:
dirty_union[cell] = true
var dirty_cells := _sorted_cells(dirty_union.keys())
if not _dirty_capacity_allows(dirty_cells):
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Dirty-cell capacity is exhausted.")
if revision >= MAX_REVISION:
return _failure(CommandResultType.Code.VALIDATION_FAILED, "Spatial revision limit is exhausted.")
var boundary := _validate_external_boundary(
_mutation_payload(&"REPLACE", entity_id, category, before_cells, after_cells)
)
if not bool(boundary[&"ok"]):
return _failure(
CommandResultType.Code.TRANSACTION_APPLY_FAILED,
String(boundary[&"message"]),
boundary.get(&"details", {})
)
for cell: Vector3i in before_cells:
var cell_slots: Dictionary = _slots.get(cell, {})
if cell_slots.get(category, &"") == entity_id:
cell_slots.erase(category)
if cell_slots.is_empty():
_slots.erase(cell)
else:
_slots[cell] = cell_slots
for cell: Vector3i in after_cells:
var cell_slots: Dictionary = _slots.get(cell, {})
cell_slots[category] = entity_id
_slots[cell] = cell_slots
_entity_cells[entity_id] = after_cells.duplicate()
_mark_changed(category, dirty_cells)
entity_cells_replaced.emit(
entity_id,
category,
before_cells.duplicate(),
after_cells.duplicate()
)
last_error = ""
return _success({
&"entity_id": entity_id,
&"category": category,
&"before_cells": before_cells,
&"cells": after_cells.duplicate(),
&"revision": revision,
&"changed": true,
})
func _validate_external_boundary(payload: Dictionary) -> Dictionary:
last_callback_report = {}
if not _compatibility_validator.is_valid():
return {&"ok": true, &"message": "", &"details": {}}
var before := _capture_internal_state()
var raw_response: Variant = _compatibility_validator.call(payload.duplicate(true))
var tampered := not _internal_state_matches(before)
if tampered:
_restore_internal_state(before)
var response_ok := false
var response_reason: StringName = &""
var response_details: Dictionary = {}
var response_valid := raw_response is Dictionary
if response_valid:
var response: Dictionary = raw_response
response_valid = _dictionary_has_only_keys(response, [&"ok", &"reason", &"details"])
var raw_ok: Variant = response.get(&"ok", null)
response_valid = response_valid and typeof(raw_ok) == TYPE_BOOL
if response.has(&"reason"):
response_valid = response_valid and typeof(response[&"reason"]) in [TYPE_STRING, TYPE_STRING_NAME]
if response.has(&"details"):
response_valid = response_valid \
and response[&"details"] is Dictionary \
and _variant_depth_is_valid(response[&"details"], 0)
if response_valid:
response_ok = bool(raw_ok)
response_reason = StringName(response.get(&"reason", &""))
response_details = (response.get(&"details", {}) as Dictionary).duplicate(true)
last_callback_report = {
&"ok": response_ok and not tampered,
&"response_valid": response_valid,
&"tampered": tampered,
&"reason": response_reason,
&"details": response_details.duplicate(true),
}
if tampered:
return {
&"ok": false,
&"message": "Compatibility callback mutated SpatialSubslotService and was rolled back.",
&"details": {&"reason": &"CALLBACK_TAMPERED", &"rollback_ok": true},
}
if not response_valid:
return {
&"ok": false,
&"message": "Compatibility callback returned an invalid response.",
&"details": {&"reason": &"INVALID_CALLBACK_RESPONSE", &"rollback_ok": true},
}
if not response_ok:
return {
&"ok": false,
&"message": "Compatibility callback rejected the spatial mutation.",
&"details": {
&"reason": response_reason if not response_reason.is_empty() else &"COMPATIBILITY_REJECTED",
&"callback_details": response_details,
&"rollback_ok": true,
},
}
return {&"ok": true, &"message": "", &"details": response_details}
func _capture_internal_state() -> Dictionary:
return {
&"mutation_active": _mutation_active,
&"compatibility_validator": _compatibility_validator,
&"revision": revision,
&"category_revisions": _category_revisions.duplicate(true),
&"slots": _slots.duplicate(true),
&"entity_cells": _entity_cells.duplicate(true),
&"entity_categories": _entity_categories.duplicate(true),
&"dirty_cells": _dirty_cells.duplicate(true),
}
func _internal_state_matches(capture: Dictionary) -> bool:
return (
_mutation_active == bool(capture[&"mutation_active"])
and _compatibility_validator == capture[&"compatibility_validator"]
and revision == int(capture[&"revision"])
and _category_revisions == capture[&"category_revisions"]
and _slots == capture[&"slots"]
and _entity_cells == capture[&"entity_cells"]
and _entity_categories == capture[&"entity_categories"]
and _dirty_cells == capture[&"dirty_cells"]
)
func _restore_internal_state(capture: Dictionary) -> void:
_mutation_active = bool(capture[&"mutation_active"])
_compatibility_validator = capture[&"compatibility_validator"]
revision = int(capture[&"revision"])
_category_revisions = (capture[&"category_revisions"] as Dictionary).duplicate(true)
_slots = (capture[&"slots"] as Dictionary).duplicate(true)
_entity_cells = (capture[&"entity_cells"] as Dictionary).duplicate(true)
_entity_categories = (capture[&"entity_categories"] as Dictionary).duplicate(true)
_dirty_cells = (capture[&"dirty_cells"] as Dictionary).duplicate(true)
func _mark_changed(category: StringName, cells: Array[Vector3i]) -> void:
revision += 1
_category_revisions[category] = revision
for cell: Vector3i in cells:
_dirty_cells[cell] = true
func _dirty_capacity_allows(cells: Array[Vector3i]) -> bool:
var additional := 0
for cell: Vector3i in cells:
if not _dirty_cells.has(cell):
additional += 1
return _dirty_cells.size() <= MAX_DIRTY_CELLS - additional
func _total_occupied_slots() -> int:
var total := 0
for entity_id: StringName in _sorted_entity_ids(_entity_cells):
var cells: Variant = _entity_cells[entity_id]
if cells is Array:
total += cells.size()
return total
static func _mutation_payload(
operation: StringName,
entity_id: StringName,
category: StringName,
before_cells: Array[Vector3i],
after_cells: Array[Vector3i]
) -> Dictionary:
return {
&"operation": operation,
&"entity_id": entity_id,
&"category": category,
&"before_cells": before_cells.duplicate(),
&"after_cells": after_cells.duplicate(),
}
static func _parse_snapshot(snapshot: Dictionary) -> Dictionary:
if not _variant_depth_is_valid(snapshot, 0):
return {&"ok": false, &"message": "Snapshot depth or container capacity is exceeded."}
if not _dictionary_has_exact_keys(snapshot, [
&"schema_version", &"revision", &"category_revisions", &"dirty_cells", &"entities",
]):
return {&"ok": false, &"message": "Snapshot top-level fields are not canonical."}
if typeof(snapshot[&"schema_version"]) != TYPE_INT \
or int(snapshot[&"schema_version"]) != SNAPSHOT_SCHEMA_VERSION:
return {&"ok": false, &"message": "Unsupported SpatialSubslotService snapshot version."}
if typeof(snapshot[&"revision"]) != TYPE_INT:
return {&"ok": false, &"message": "Snapshot revision must be an integer."}
var next_revision := int(snapshot[&"revision"])
if next_revision < 0 or next_revision > MAX_REVISION:
return {&"ok": false, &"message": "Snapshot revision is outside the supported range."}
if not snapshot[&"category_revisions"] is Dictionary \
or not _dictionary_has_exact_keys(snapshot[&"category_revisions"], CATEGORIES):
return {&"ok": false, &"message": "Snapshot category revisions are malformed."}
var next_category_revisions: Dictionary = {}
var latest_category_revision := 0
for category: StringName in CATEGORIES:
var raw_category_revision: Variant = snapshot[&"category_revisions"][category]
if typeof(raw_category_revision) != TYPE_INT \
or int(raw_category_revision) < 0 \
or int(raw_category_revision) > next_revision:
return {&"ok": false, &"message": "Snapshot category revision is invalid."}
next_category_revisions[category] = int(raw_category_revision)
latest_category_revision = max(latest_category_revision, int(raw_category_revision))
if next_revision > 0 and latest_category_revision != next_revision:
return {&"ok": false, &"message": "Snapshot has no category at the global revision."}
if not snapshot[&"entities"] is Array or snapshot[&"entities"].size() > MAX_ENTITIES:
return {&"ok": false, &"message": "Snapshot entity collection is malformed or too large."}
var next_slots: Dictionary = {}
var next_entity_cells: Dictionary = {}
var next_entity_categories: Dictionary = {}
var total_slots := 0
var previous_entity_id := ""
for raw_entity: Variant in snapshot[&"entities"]:
if not raw_entity is Dictionary or not _dictionary_has_exact_keys(raw_entity, [
&"entity_id", &"category", &"cells",
]):
return {&"ok": false, &"message": "Snapshot entity entry is malformed."}
if typeof(raw_entity[&"entity_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]:
return {&"ok": false, &"message": "Snapshot entity ID must be a string."}
var entity_id := StringName(raw_entity[&"entity_id"])
var entity_text := String(entity_id)
if not _stable_id_is_valid(entity_id) \
or (not previous_entity_id.is_empty() and entity_text <= previous_entity_id):
return {&"ok": false, &"message": "Snapshot entity IDs are invalid, duplicated, or unordered."}
if not _category_is_valid(raw_entity[&"category"]):
return {&"ok": false, &"message": "Snapshot entity category is invalid."}
var category := StringName(raw_entity[&"category"])
var cells_validation := _validate_cells(raw_entity[&"cells"])
if not bool(cells_validation[&"ok"]):
return {&"ok": false, &"message": String(cells_validation[&"message"])}
var cells: Array[Vector3i] = cells_validation[&"cells"]
if raw_entity[&"cells"] != cells:
return {&"ok": false, &"message": "Snapshot entity cells are not canonically ordered."}
if total_slots > MAX_TOTAL_SLOTS - cells.size():
return {&"ok": false, &"message": "Snapshot occupied subslot capacity is exceeded."}
for cell: Vector3i in cells:
var cell_slots: Dictionary = next_slots.get(cell, {})
if cell_slots.has(category):
return {&"ok": false, &"message": "Snapshot contains a duplicate category slot."}
cell_slots[category] = entity_id
next_slots[cell] = cell_slots
next_entity_cells[entity_id] = cells
next_entity_categories[entity_id] = category
total_slots += cells.size()
previous_entity_id = entity_text
if not snapshot[&"dirty_cells"] is Array or snapshot[&"dirty_cells"].size() > MAX_DIRTY_CELLS:
return {&"ok": false, &"message": "Snapshot dirty-cell collection is malformed or too large."}
var next_dirty: Dictionary = {}
var previous_dirty: Variant = null
for raw_cell: Variant in snapshot[&"dirty_cells"]:
if not _cell_is_valid(raw_cell) or next_dirty.has(raw_cell):
return {&"ok": false, &"message": "Snapshot dirty cell is invalid or duplicated."}
var cell: Vector3i = raw_cell
if previous_dirty is Vector3i and not _cell_less(previous_dirty, cell):
return {&"ok": false, &"message": "Snapshot dirty cells are not canonically ordered."}
next_dirty[cell] = true
previous_dirty = cell
if next_revision == 0 and (not next_entity_cells.is_empty() or not next_dirty.is_empty()):
return {&"ok": false, &"message": "Zero revision cannot own entities or dirty cells."}
for category: StringName in CATEGORIES:
if int(next_category_revisions[category]) == 0:
for entity_id: StringName in _sorted_entity_ids(next_entity_cells):
if next_entity_categories[entity_id] == category:
return {&"ok": false, &"message": "Occupied category must have a positive revision."}
return {
&"ok": true,
&"revision": next_revision,
&"category_revisions": next_category_revisions,
&"slots": next_slots,
&"entity_cells": next_entity_cells,
&"entity_categories": next_entity_categories,
&"dirty_cells": next_dirty,
}
static func _parse_identity(entity_id_value: Variant, category_value: Variant) -> Dictionary:
if not _stable_id_is_valid(entity_id_value):
return {&"ok": false, &"message": "Spatial entity ID is invalid."}
if not _category_is_valid(category_value):
return {&"ok": false, &"message": "Spatial category is invalid."}
return {
&"ok": true,
&"entity_id": StringName(entity_id_value),
&"category": StringName(category_value),
}
static func _validate_cells(value: Variant) -> Dictionary:
if not value is Array:
return {&"ok": false, &"message": "Spatial cells must be an Array.", &"cells": []}
if value.is_empty() or value.size() > MAX_CELLS_PER_ENTITY:
return {
&"ok": false,
&"message": "Spatial cells must be non-empty and within the per-entity limit.",
&"cells": [],
}
var seen: Dictionary = {}
var cells: Array[Vector3i] = []
for raw_cell: Variant in value:
if not _cell_is_valid(raw_cell):
return {&"ok": false, &"message": "Spatial cell type, coordinate, or layer is invalid.", &"cells": []}
var cell: Vector3i = raw_cell
if seen.has(cell):
return {&"ok": false, &"message": "Spatial cells contain a duplicate.", &"cells": []}
seen[cell] = true
cells.append(cell)
cells.sort_custom(_cell_less)
return {&"ok": true, &"message": "", &"cells": cells}
static func _stable_id_is_valid(value: Variant) -> bool:
if typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]:
return false
var text := String(value)
if text.is_empty() or text.length() > MAX_ENTITY_ID_LENGTH:
return false
for index: int in range(text.length()):
var code := text.unicode_at(index)
var allowed := (
code >= 48 and code <= 57
or code >= 65 and code <= 90
or code >= 97 and code <= 122
or code in [45, 46, 58, 64, 95]
)
if not allowed:
return false
return true
static func _category_is_valid(value: Variant) -> bool:
return typeof(value) in [TYPE_STRING, TYPE_STRING_NAME] and CATEGORIES.has(StringName(value))
static func _cell_is_valid(value: Variant) -> bool:
if not value is Vector3i:
return false
var cell: Vector3i = value
return (
cell.z in [LAYER_FRONT, LAYER_BACK]
and cell.x >= -MAX_COORDINATE_ABS
and cell.x <= MAX_COORDINATE_ABS
and cell.y >= -MAX_COORDINATE_ABS
and cell.y <= MAX_COORDINATE_ABS
)
static func _sorted_entity_ids(source: Dictionary) -> Array[StringName]:
var result: Array[StringName] = []
for raw_entity_id: Variant in source:
result.append(StringName(raw_entity_id))
result.sort_custom(_name_less)
return result
static func _copy_cells(source: Array) -> Array[Vector3i]:
var result: Array[Vector3i] = []
for raw_cell: Variant in source:
if raw_cell is Vector3i:
result.append(raw_cell)
result.sort_custom(_cell_less)
return result
static func _sorted_cells(source: Variant) -> Array[Vector3i]:
var result: Array[Vector3i] = []
if not source is Array:
return result
for raw_cell: Variant in source:
if raw_cell is Vector3i:
result.append(raw_cell)
result.sort_custom(_cell_less)
return result
static func _cell_less(left: Vector3i, right: Vector3i) -> bool:
if left.y != right.y:
return left.y < right.y
if left.x != right.x:
return left.x < right.x
return left.z < right.z
static func _name_less(left: StringName, right: StringName) -> bool:
return String(left) < String(right)
static func _dictionary_has_exact_keys(source: Dictionary, expected: Array) -> bool:
if source.size() != expected.size():
return false
for key: Variant in expected:
if not source.has(key):
return false
return true
static func _dictionary_has_only_keys(source: Dictionary, allowed: Array) -> bool:
for key: Variant in source:
if typeof(key) not in [TYPE_STRING, TYPE_STRING_NAME] or not allowed.has(StringName(key)):
return false
return source.has(&"ok") or source.has("ok")
static func _variant_depth_is_valid(value: Variant, depth: int) -> bool:
if depth > MAX_SNAPSHOT_DEPTH:
return false
if value is Array:
if value.size() > MAX_CONTAINER_ITEMS:
return false
for child: Variant in value:
if not _variant_depth_is_valid(child, depth + 1):
return false
elif value is Dictionary:
if value.size() > MAX_CONTAINER_ITEMS:
return false
for key: Variant in value:
if not _variant_depth_is_valid(key, depth + 1) \
or not _variant_depth_is_valid(value[key], depth + 1):
return false
elif typeof(value) in [TYPE_OBJECT, TYPE_CALLABLE, TYPE_SIGNAL, TYPE_RID]:
return false
return true
func _success(details: Dictionary = {}) -> CommandResult:
last_error = ""
return CommandResultType.succeeded(0, details)
func _failure(code: CommandResult.Code, message: String, details: Dictionary = {}) -> CommandResult:
last_error = message
return CommandResultType.failed(code, 0, message, details)