Phase 7: netfox + godot-jolt stack upgrade
Stack installed: - netfox v1.35.3 (core + extras + noray + internals) - godot-jolt v0.16.0-stable Architecture: - Server: ENet transport (works headless, no netfox deps) - Client/Editor: netfox rollback (RollbackSynchronizer, TickInterpolator) New/modified: - docs/migration-netfox-plan.md — migration architecture - scripts/network/network_manager.gd — netfox-aware ENet fallback - scripts/network/player.gd — clean base player - client/characters/player_netfox.gd — rollback player w/ WeaponManager - client/characters/input/player_net_input.gd — BaseNetInput subclass - client/characters/character/fps_character_controller.gd — netfox input feed - client/weapons/ — weapon data, registry, TacticalWeaponHitscan, WeaponManager - client/scripts/round_replicator.gd — client-side round state bridge - server/scripts/round_manager.gd — improved state machine - server/scripts/plugin_api/plugin_manager.gd — refined plugin system - config: enemy_tag, ally_tag for meatball targeting Removed: old C++ SimulationServer GDExtension (replaced by netfox rollback)
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
extends RefCounted
|
||||
class_name PropertyCache
|
||||
|
||||
var root: Node
|
||||
var _cache: Dictionary = {}
|
||||
|
||||
static var _logger: NetfoxLogger = NetfoxLogger._for_netfox("PropertyCache")
|
||||
|
||||
func _init(p_root: Node):
|
||||
root = p_root
|
||||
|
||||
func get_entry(path: String) -> PropertyEntry:
|
||||
if not _cache.has(path):
|
||||
var parsed = PropertyEntry.parse(root, path)
|
||||
if not parsed.is_valid():
|
||||
_logger.warning("Invalid property path: %s", [path])
|
||||
_cache[path] = parsed
|
||||
return _cache[path]
|
||||
|
||||
func properties() -> Array:
|
||||
var result: Array[PropertyEntry]
|
||||
# Can be slow, but no other way to do this with type-safety
|
||||
# See: https://github.com/godotengine/godot/issues/72627
|
||||
result.assign(_cache.values())
|
||||
return result
|
||||
|
||||
func clear():
|
||||
_cache.clear()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cjb41satp02xy
|
||||
@@ -0,0 +1,36 @@
|
||||
extends RefCounted
|
||||
class_name _PropertyConfig
|
||||
|
||||
var _properties: Array[PropertyEntry] = []
|
||||
var _auth_properties: Dictionary = {} # Peer (int) to owned properties (Array[PropertyEntry])
|
||||
|
||||
var local_peer_id: int
|
||||
|
||||
func clear() -> void:
|
||||
_properties.clear()
|
||||
_auth_properties.clear()
|
||||
|
||||
func set_properties(p_properties: Array[PropertyEntry]) -> void:
|
||||
clear()
|
||||
_properties.assign(p_properties)
|
||||
|
||||
func set_properties_from_paths(property_paths: Array[String], property_cache: PropertyCache) -> void:
|
||||
clear()
|
||||
for path in property_paths:
|
||||
_properties.append(property_cache.get_entry(path))
|
||||
|
||||
func get_properties() -> Array[PropertyEntry]:
|
||||
return _properties
|
||||
|
||||
func get_owned_properties() -> Array[PropertyEntry]:
|
||||
return get_properties_owned_by(local_peer_id)
|
||||
|
||||
func get_properties_owned_by(peer: int) -> Array[PropertyEntry]:
|
||||
if not _auth_properties.has(peer):
|
||||
var owned_properties: Array[PropertyEntry] = []
|
||||
for property_entry in _properties:
|
||||
if property_entry.node.get_multiplayer_authority() == peer:
|
||||
owned_properties.append(property_entry)
|
||||
_auth_properties[peer] = owned_properties
|
||||
|
||||
return _auth_properties[peer]
|
||||
@@ -0,0 +1 @@
|
||||
uid://b58ojksbbkrvh
|
||||
@@ -0,0 +1,51 @@
|
||||
extends RefCounted
|
||||
class_name PropertyEntry
|
||||
|
||||
var _path: String
|
||||
var node: Node
|
||||
var property: String
|
||||
|
||||
static var _logger := NetfoxLogger._for_netfox("PropertyEntry")
|
||||
|
||||
func get_value() -> Variant:
|
||||
return node.get_indexed(property)
|
||||
|
||||
func set_value(value):
|
||||
node.set_indexed(property, value)
|
||||
|
||||
func is_valid() -> bool:
|
||||
if not node or not is_instance_valid(node):
|
||||
# Node is invalid
|
||||
return false
|
||||
|
||||
# Return true if node has given property
|
||||
return node.get_property_list()\
|
||||
.any(func(it): return it["name"] == property)
|
||||
|
||||
func _to_string() -> String:
|
||||
return _path
|
||||
|
||||
static func parse(root: Node, path: String) -> PropertyEntry:
|
||||
var result = PropertyEntry.new()
|
||||
result.node = root.get_node(NodePath(path))
|
||||
result.property = path.erase(0, path.find(":") + 1)
|
||||
result._path = path
|
||||
return result
|
||||
|
||||
static func make_path(root: Node, node: Variant, property: String) -> String:
|
||||
var node_path := ""
|
||||
|
||||
if node is String:
|
||||
node_path = node
|
||||
elif node is NodePath:
|
||||
node_path = str(node)
|
||||
elif node is Node:
|
||||
node_path = str(root.get_path_to(node))
|
||||
else:
|
||||
_logger.error("Can't stringify node reference: %s", [node])
|
||||
return ""
|
||||
|
||||
if node_path == ".":
|
||||
node_path = ""
|
||||
|
||||
return "%s:%s" % [node_path, property]
|
||||
@@ -0,0 +1 @@
|
||||
uid://cixo40ot0fqqv
|
||||
@@ -0,0 +1,28 @@
|
||||
extends _HistoryBuffer
|
||||
class_name _PropertyHistoryBuffer
|
||||
|
||||
func get_snapshot(tick: int) -> _PropertySnapshot:
|
||||
if _buffer.has(tick):
|
||||
return _buffer[tick]
|
||||
else:
|
||||
return _PropertySnapshot.new()
|
||||
|
||||
func set_snapshot(tick: int, data) -> void:
|
||||
if data is Dictionary:
|
||||
var snapshot := _PropertySnapshot.from_dictionary(data)
|
||||
super(tick, snapshot)
|
||||
elif data is _PropertySnapshot:
|
||||
super(tick, data)
|
||||
else:
|
||||
push_error("Data not a PropertSnapshot! %s" % [data])
|
||||
|
||||
func get_history(tick: int) -> _PropertySnapshot:
|
||||
var snapshot = super(tick)
|
||||
|
||||
return snapshot if snapshot else _PropertySnapshot.new()
|
||||
|
||||
func trim(earliest_tick_to_keep: int = NetworkRollback.history_start) -> void:
|
||||
super(earliest_tick_to_keep)
|
||||
|
||||
func merge(data: _PropertySnapshot, tick:int) -> void:
|
||||
set_snapshot(tick, get_snapshot(tick).merge(data))
|
||||
@@ -0,0 +1 @@
|
||||
uid://dlkog3qntq03x
|
||||
@@ -0,0 +1,86 @@
|
||||
extends RefCounted
|
||||
class_name _PropertySnapshot
|
||||
|
||||
# Maps property paths to their values
|
||||
# Dictionary[String, Variant]
|
||||
var _snapshot: Dictionary = {}
|
||||
|
||||
static var _logger := NetfoxLogger._for_netfox("PropertySnapshot")
|
||||
|
||||
func as_dictionary() -> Dictionary:
|
||||
return _snapshot.duplicate()
|
||||
|
||||
static func from_dictionary(data: Dictionary) -> _PropertySnapshot:
|
||||
return _PropertySnapshot.new(data)
|
||||
|
||||
func set_value(property_path: String, data: Variant) -> void:
|
||||
_snapshot[property_path] = data
|
||||
|
||||
func get_value(property_path: String) -> Variant:
|
||||
return _snapshot.get(property_path)
|
||||
|
||||
func properties() -> Array:
|
||||
return _snapshot.keys()
|
||||
|
||||
func has(property_path: String) -> bool:
|
||||
return _snapshot.has(property_path)
|
||||
|
||||
func size() -> int:
|
||||
return _snapshot.size()
|
||||
|
||||
func equals(other: _PropertySnapshot):
|
||||
return _snapshot == other._snapshot
|
||||
|
||||
func is_empty() -> bool:
|
||||
return _snapshot.is_empty()
|
||||
|
||||
func apply(cache: PropertyCache) -> void:
|
||||
for property_path in _snapshot:
|
||||
var property_entry := cache.get_entry(property_path)
|
||||
var value = _snapshot[property_path]
|
||||
property_entry.set_value(value)
|
||||
|
||||
func merge(data: _PropertySnapshot) -> _PropertySnapshot:
|
||||
var result := _snapshot.duplicate()
|
||||
for key in data.as_dictionary():
|
||||
result[key] = data._snapshot[key]
|
||||
|
||||
return _PropertySnapshot.from_dictionary(result)
|
||||
|
||||
func make_patch(data: _PropertySnapshot) -> _PropertySnapshot:
|
||||
var result := {}
|
||||
|
||||
for property_path in data.properties():
|
||||
var old_property = get_value(property_path)
|
||||
var new_property = data.get_value(property_path)
|
||||
|
||||
if old_property != new_property:
|
||||
result[property_path] = new_property
|
||||
|
||||
return _PropertySnapshot.from_dictionary(result)
|
||||
|
||||
func sanitize(sender: int, property_cache: PropertyCache) -> void:
|
||||
var sanitized := {}
|
||||
|
||||
for property in _snapshot.keys():
|
||||
var property_entry := property_cache.get_entry(property)
|
||||
var authority := property_entry.node.get_multiplayer_authority()
|
||||
|
||||
if authority == sender:
|
||||
sanitized[property] = _snapshot[property]
|
||||
else:
|
||||
_logger.warning(
|
||||
"Received data for property %s, owned by %s, from sender %s",
|
||||
[ property, authority, sender ]
|
||||
)
|
||||
|
||||
_snapshot = sanitized
|
||||
|
||||
static func extract(properties: Array[PropertyEntry]) -> _PropertySnapshot:
|
||||
var result = {}
|
||||
for property in properties:
|
||||
result[property.to_string()] = property.get_value()
|
||||
return _PropertySnapshot.from_dictionary(result)
|
||||
|
||||
func _init(p_snapshot: Dictionary = {}) -> void:
|
||||
_snapshot = p_snapshot
|
||||
@@ -0,0 +1 @@
|
||||
uid://d2dgbafx6338f
|
||||
Reference in New Issue
Block a user