Fresh start: replace with naxIO/netfox-cs-sample foundation
Complete replacement of the tactical-shooter project with the netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built with Godot 4 and netfox. ## What's new - Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu - 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP - Bomb plant/defuse with 2 bombsites - Flashbang & smoke grenades - Proper netfox rollback netcode at 64 tick - Network popup UI for host/join - HUD, crosshair, round timer, scoreboard - All netfox singletons registered as autoloads (works in exported builds) ## Architecture - Listen-server (host from client, no dedicated server binary) - Multiplayer-fps game lives at examples/multiplayer-fps/ - Netfox addons registered as autoloads for exported build compat - Godot 4.7 with Forward+ renderer ## Removed - Old headless-server architecture (client_main, server_main, player.gd, etc.) - Custom netfox bootstrap with ENet fallback - Old ChaffGames FPS template (2,420 lines, 844 KB) - SimulationServer GDExtension stub - Godot-jolt physics (netfox sample uses default Godot physics) - Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc. - Server browser API Python venv (87 MB) - test_range map and modular assets ## Preserved - Git history - Server config at config/default_server_config.cfg - Windows export preset - Build directory (gitignored) Co-authored-by: naxIO <naxIO@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name():
|
||||
return "PeerVisibilityFilter"
|
||||
|
||||
func suite():
|
||||
var peers: Array[int] = [1, 2, 3, 4]
|
||||
var peers_packed_array := PackedInt32Array(peers)
|
||||
|
||||
define("Visibility override", func():
|
||||
test("should return all peers on default visibility", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = true
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
assert_that(filter.get_visible_peers()).is_equal_to(peers)
|
||||
assert_that(filter.get_rpc_target_peers()).is_equal_to([0])
|
||||
)
|
||||
|
||||
test("should return no peers on default invisibility", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = false
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
assert_that(filter.get_visible_peers()).is_empty()
|
||||
assert_that(filter.get_rpc_target_peers()).is_empty()
|
||||
)
|
||||
|
||||
test("should return force-visible peers on default invisibility", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = false
|
||||
filter.set_visibility_for(2, true)
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
expect(filter.get_visibility_for(2), "Peer#2 should be visible!")
|
||||
expect_not(filter.get_visibility_for(1), "Peer#1 should not be visible!")
|
||||
assert_that(filter.get_visible_peers()).is_equal_to([2])
|
||||
assert_that(filter.get_rpc_target_peers()).is_equal_to([2])
|
||||
)
|
||||
|
||||
test("should not return force-invisible peers on default visibility", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = true
|
||||
filter.set_visibility_for(2, false)
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
expect_not(filter.get_visibility_for(2), "Peer#2 should not be visible!")
|
||||
expect(filter.get_visibility_for(1), "Peer#1 should be visible!")
|
||||
assert_that(filter.get_visible_peers()).is_equal_to([1, 3, 4])
|
||||
assert_that(filter.get_rpc_target_peers()).is_equal_to([-2])
|
||||
)
|
||||
|
||||
test("should return peers with multiple excludes", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = true
|
||||
filter.set_visibility_for(2, false)
|
||||
filter.set_visibility_for(4, false)
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
expect_not(filter.get_visibility_for(2), "Peer#2 should not be visible!")
|
||||
expect(filter.get_visibility_for(1), "Peer#1 should be visible!")
|
||||
assert_that(filter.get_visible_peers()).is_equal_to([1, 3])
|
||||
assert_that(filter.get_rpc_target_peers()).is_equal_to([1, 3])
|
||||
)
|
||||
)
|
||||
|
||||
define("Visibility filters", func():
|
||||
test("should pass through on true", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = true
|
||||
filter.add_visibility_filter(func(_peer: int): return true)
|
||||
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
assert_that(filter.get_visible_peers()).is_equal_to(peers)
|
||||
)
|
||||
|
||||
test("should exclude on false", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = true
|
||||
filter.add_visibility_filter(func(_peer: int): return false)
|
||||
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
assert_that(filter.get_visible_peers()).is_empty()
|
||||
)
|
||||
|
||||
test("should exclude if any returns false", func():
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = true
|
||||
filter.add_visibility_filter(func(_peer: int): return true)
|
||||
filter.add_visibility_filter(func(_peer: int): return false)
|
||||
|
||||
filter.update_visibility(peers_packed_array)
|
||||
|
||||
assert_that(filter.get_visible_peers()).is_empty()
|
||||
)
|
||||
)
|
||||
|
||||
test("filter should have precendence over override", func():
|
||||
var excluded_peer := 2
|
||||
var filter := PeerVisibilityFilter.new()
|
||||
filter.default_visibility = false
|
||||
filter.set_visibility_for(excluded_peer, true)
|
||||
filter.add_visibility_filter(func(peer: int):
|
||||
if peer == excluded_peer: return false
|
||||
else: return true
|
||||
)
|
||||
|
||||
filter.update_visibility(peers_packed_array)
|
||||
assert_that(filter.get_visible_peers()).is_empty()
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bxy7dx4ktuwm7
|
||||
@@ -0,0 +1,57 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "PropertyConfig"
|
||||
|
||||
const LOCAL_PEER := 1
|
||||
const REMOTE_PEER := 2
|
||||
|
||||
func suite():
|
||||
var local_node := SnapshotFixtures.state_node()
|
||||
var remote_node := SnapshotFixtures.state_node()
|
||||
remote_node.set_multiplayer_authority(REMOTE_PEER)
|
||||
|
||||
test("should get locally owned properties", func():
|
||||
var properties := SnapshotFixtures.state_propery_entries(local_node)
|
||||
var config := _PropertyConfig.new()
|
||||
config.set_properties(properties)
|
||||
config.local_peer_id = LOCAL_PEER
|
||||
|
||||
expect_equal(config.get_owned_properties(), properties)
|
||||
expect_equal(config.get_properties_owned_by(LOCAL_PEER), properties)
|
||||
expect_empty(config.get_properties_owned_by(REMOTE_PEER))
|
||||
)
|
||||
|
||||
test("should get remote owned properties", func():
|
||||
var properties := SnapshotFixtures.state_propery_entries(remote_node)
|
||||
var config := _PropertyConfig.new()
|
||||
config.set_properties(properties)
|
||||
config.local_peer_id = REMOTE_PEER
|
||||
|
||||
expect_equal(config.get_properties_owned_by(REMOTE_PEER), properties)
|
||||
expect_empty(config.get_properties_owned_by(LOCAL_PEER))
|
||||
)
|
||||
|
||||
test("should get remote owned properties after ownership change", func():
|
||||
var properties := SnapshotFixtures.state_properties()
|
||||
var cache := PropertyCache.new(remote_node)
|
||||
var config := _PropertyConfig.new()
|
||||
config.set_properties_from_paths(SnapshotFixtures.state_properties(), cache)
|
||||
config.local_peer_id = LOCAL_PEER
|
||||
|
||||
expect_empty(config.get_owned_properties())
|
||||
|
||||
# Update ownership
|
||||
remote_node.set_multiplayer_authority(1)
|
||||
config.set_properties_from_paths(SnapshotFixtures.state_properties(), cache)
|
||||
|
||||
# Convert to a list of property paths for easier comparison
|
||||
var owned_properties := config.get_owned_properties()\
|
||||
.map(func(it): return str(it))
|
||||
expect_equal(owned_properties, properties)
|
||||
)
|
||||
|
||||
on_finish.connect(func():
|
||||
local_node.queue_free()
|
||||
remote_node.queue_free()
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://wucpfwpo6w3n
|
||||
@@ -0,0 +1,45 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "PropertyEntry"
|
||||
|
||||
func test_should_be_valid_on_known_property():
|
||||
var property_entry := PropertyEntry.new()
|
||||
property_entry.node = TestNode.new()
|
||||
property_entry.property = "known_property"
|
||||
|
||||
expect(property_entry.is_valid(), "Property entry is invalid!")
|
||||
|
||||
func test_should_be_valid_on_null_property():
|
||||
var property_entry := PropertyEntry.new()
|
||||
property_entry.node = TestNode.new()
|
||||
property_entry.property = "null_property"
|
||||
|
||||
expect(property_entry.is_valid(), "Property entry is invalid!")
|
||||
|
||||
func test_should_be_invalid_on_unknown_property():
|
||||
var property_entry := PropertyEntry.new()
|
||||
property_entry.node = TestNode.new()
|
||||
property_entry.property = "unknown_property"
|
||||
|
||||
expect_not(property_entry.is_valid(), "Property entry is valid!")
|
||||
|
||||
func test_should_be_invalid_on_unknown_node():
|
||||
var property_entry := PropertyEntry.new()
|
||||
property_entry.node = null
|
||||
property_entry.property = "known_property"
|
||||
|
||||
expect_not(property_entry.is_valid(), "Property entry is valid!")
|
||||
|
||||
func test_should_be_invalid_on_invalid_node():
|
||||
var property_entry := PropertyEntry.new()
|
||||
property_entry.node = TestNode.new()
|
||||
property_entry.property = "known_property"
|
||||
|
||||
property_entry.node.free()
|
||||
|
||||
expect_not(property_entry.is_valid(), "Property entry is valid!")
|
||||
|
||||
class TestNode extends Node:
|
||||
var known_property := ""
|
||||
var null_property = null
|
||||
@@ -0,0 +1 @@
|
||||
uid://ci3ql26p3vwsi
|
||||
@@ -0,0 +1,86 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "PropertySnapshot"
|
||||
|
||||
func test_diff_should_be_empty():
|
||||
# Given
|
||||
var from := _PropertySnapshot.from_dictionary({
|
||||
"foo": 15,
|
||||
"bar": 18,
|
||||
})
|
||||
|
||||
var to := _PropertySnapshot.from_dictionary({
|
||||
"foo": 15,
|
||||
"bar": 18,
|
||||
})
|
||||
|
||||
var expected := _PropertySnapshot.from_dictionary({})
|
||||
|
||||
# When
|
||||
var actual := from.make_patch(to)
|
||||
|
||||
# Then
|
||||
expect_equal(actual.as_dictionary(), expected.as_dictionary())
|
||||
|
||||
func test_diff_should_add_unknown():
|
||||
# Given
|
||||
var from := _PropertySnapshot.from_dictionary({
|
||||
"foo": 15,
|
||||
})
|
||||
|
||||
var to := _PropertySnapshot.from_dictionary({
|
||||
"foo": 15,
|
||||
"bar": 18,
|
||||
})
|
||||
|
||||
var expected := _PropertySnapshot.from_dictionary({
|
||||
"bar": 18,
|
||||
})
|
||||
|
||||
# When
|
||||
var actual := from.make_patch(to)
|
||||
|
||||
# Then
|
||||
expect_equal(actual.as_dictionary(), expected.as_dictionary())
|
||||
|
||||
func test_diff_should_add_differing():
|
||||
# Given
|
||||
var from := _PropertySnapshot.from_dictionary({
|
||||
"foo": 15,
|
||||
"bar": 18,
|
||||
})
|
||||
|
||||
var to := _PropertySnapshot.from_dictionary({
|
||||
"foo": 35,
|
||||
"bar": 18,
|
||||
})
|
||||
|
||||
var expected := _PropertySnapshot.from_dictionary({
|
||||
"foo": 35
|
||||
})
|
||||
|
||||
# When
|
||||
var actual := from.make_patch(to)
|
||||
|
||||
# Then
|
||||
expect_equal(actual.as_dictionary(), expected.as_dictionary())
|
||||
|
||||
func test_diff_should_exclude_removed():
|
||||
# Given
|
||||
var from := _PropertySnapshot.from_dictionary({
|
||||
"foo": 15,
|
||||
"bar": 18,
|
||||
})
|
||||
|
||||
var to := _PropertySnapshot.from_dictionary({
|
||||
"foo": 15,
|
||||
})
|
||||
|
||||
var expected := _PropertySnapshot.from_dictionary({})
|
||||
|
||||
# When
|
||||
var actual := from.make_patch(to)
|
||||
|
||||
# Then
|
||||
expect_equal(actual.as_dictionary(), expected.as_dictionary())
|
||||
@@ -0,0 +1 @@
|
||||
uid://cmu1no787kjyt
|
||||
@@ -0,0 +1,34 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name():
|
||||
return "RewindableAction"
|
||||
|
||||
var rewindable_action: RewindableAction
|
||||
|
||||
func before_case(__) -> void:
|
||||
rewindable_action = RewindableAction.new()
|
||||
# TODO(vest): Some good way to add nodes to the tree
|
||||
await NetworkTime.get_tree().process_frame
|
||||
NetworkTime.get_tree().root.add_child(rewindable_action)
|
||||
|
||||
func after_case(__) -> void:
|
||||
rewindable_action.free()
|
||||
|
||||
func test_should_empty_queue_after_loop():
|
||||
# Run as client
|
||||
rewindable_action.set_multiplayer_authority(2)
|
||||
|
||||
# Prepare changes
|
||||
var tickset := _Set.of([0])
|
||||
var data := _TicksetSerializer.serialize(0, 4, tickset)
|
||||
NetworkMocks.set_tick(4, 4)
|
||||
|
||||
# Run first loop
|
||||
rewindable_action._submit_state(data) # Server sends data
|
||||
NetworkMocks.run_network_tick_loop()
|
||||
expect(rewindable_action.has_confirmed(), "RewindableAction was not confirmed!")
|
||||
|
||||
# Run second loop
|
||||
rewindable_action._submit_state(data) # Server sends data again
|
||||
NetworkMocks.run_network_tick_loop()
|
||||
expect_not(rewindable_action.has_confirmed(), "RewindableAction was redundantly confirmed!")
|
||||
@@ -0,0 +1 @@
|
||||
uid://bjj3hqe3obauq
|
||||
@@ -0,0 +1,22 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "RollbackSynchronizer"
|
||||
|
||||
func suite():
|
||||
# Messy to set up, keeping cases for later
|
||||
define("Input age and predicting", func():
|
||||
test("should return -1 on no input", func(): todo())
|
||||
test("should return 0 on recent input", func(): todo())
|
||||
test("should return positive on old input", func(): todo())
|
||||
)
|
||||
|
||||
define("get_last_known_input()", func():
|
||||
test("should return -1 for no input", func(): todo())
|
||||
test("should return latest", func(): todo())
|
||||
)
|
||||
|
||||
define("get_last_known_state()", func():
|
||||
test("should return -1 for no state", func(): todo())
|
||||
test("should return latest", func(): todo())
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://5ufnoe5bk8kt
|
||||
@@ -0,0 +1,53 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "NetworkRollback"
|
||||
|
||||
var network_rollback: _NetworkRollback
|
||||
var mutated_node: Node
|
||||
|
||||
func before_case(__):
|
||||
network_rollback = _NetworkRollback.new()
|
||||
mutated_node = Node.new()
|
||||
|
||||
func after_case(__):
|
||||
network_rollback.queue_free()
|
||||
mutated_node.queue_free()
|
||||
|
||||
func suite() -> void:
|
||||
define("mutate()", func():
|
||||
test("should be mutated after", func():
|
||||
# Given
|
||||
network_rollback.mutate(mutated_node, 8)
|
||||
|
||||
# When + Then
|
||||
expect(network_rollback.is_mutated(mutated_node, 10))
|
||||
expect_not(network_rollback.is_just_mutated(mutated_node, 10))
|
||||
)
|
||||
|
||||
test("should just be mutated", func():
|
||||
# Given
|
||||
network_rollback.mutate(mutated_node, 8)
|
||||
|
||||
# When + Then
|
||||
expect(network_rollback.is_mutated(mutated_node, 8))
|
||||
expect(network_rollback.is_just_mutated(mutated_node, 8))
|
||||
)
|
||||
|
||||
test("should not be mutated after", func():
|
||||
# Given
|
||||
network_rollback.mutate(mutated_node, 8)
|
||||
|
||||
# When + Then
|
||||
expect_not(network_rollback.is_mutated(mutated_node, 4))
|
||||
expect_not(network_rollback.is_just_mutated(mutated_node, 4))
|
||||
)
|
||||
|
||||
test("unknown should not be mutated", func():
|
||||
# Given nothing
|
||||
|
||||
# Then
|
||||
expect_not(network_rollback.is_mutated(mutated_node, 8))
|
||||
expect_not(network_rollback.is_just_mutated(mutated_node, 8))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bvy32kc3ngile
|
||||
@@ -0,0 +1,138 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "NetworkSchemas"
|
||||
|
||||
func suite() -> void:
|
||||
var has_half := (Engine.get_version_info().hex >= 0x040400) as bool
|
||||
|
||||
var cases := [
|
||||
["variant", NetworkSchemas.variant(), 32, 12],
|
||||
["string", NetworkSchemas.string(), "hi!!", 8],
|
||||
|
||||
["bool8", NetworkSchemas.bool8(), true, 1],
|
||||
|
||||
["int8", NetworkSchemas.int8(), 107, 1],
|
||||
["int16", NetworkSchemas.int16(), 107, 2],
|
||||
["int32", NetworkSchemas.int32(), 107, 4],
|
||||
["int64", NetworkSchemas.int64(), 107, 8],
|
||||
|
||||
["uint8", NetworkSchemas.uint8(), 107, 1],
|
||||
["uint16", NetworkSchemas.uint16(), 107, 2],
|
||||
["uint32", NetworkSchemas.uint32(), 107, 4],
|
||||
["uint64", NetworkSchemas.uint64(), 107, 8],
|
||||
|
||||
# Test values are max representable values in `n` bytes
|
||||
["varuint 8", NetworkSchemas.varuint(), 127, 1],
|
||||
["varuint 16", NetworkSchemas.varuint(), 16383, 2],
|
||||
["varuint 24", NetworkSchemas.varuint(), 2097151, 3],
|
||||
["varuint 32", NetworkSchemas.varuint(), 268435455, 4],
|
||||
["varuint 40", NetworkSchemas.varuint(), 34359738367, 5],
|
||||
["varuint 48", NetworkSchemas.varuint(), 4398046511103, 6],
|
||||
["varuint 56", NetworkSchemas.varuint(), 562949953421311, 7],
|
||||
["varuint 64", NetworkSchemas.varuint(), 72057594037927935, 8],
|
||||
|
||||
["sfrac8", NetworkSchemas.sfrac8(), -63. / 255., 1],
|
||||
["sfrac16", NetworkSchemas.sfrac16(), -63. / 255., 2],
|
||||
["sfrac32", NetworkSchemas.sfrac32(), -63. / 255., 4],
|
||||
|
||||
["ufrac8", NetworkSchemas.ufrac8(), 63. / 255., 1],
|
||||
["ufrac16", NetworkSchemas.ufrac16(), 63. / 255., 2],
|
||||
["ufrac32", NetworkSchemas.ufrac32(), 63. / 255., 4],
|
||||
|
||||
["degrees8", NetworkSchemas.degrees8(), 127. / 255. * 360., 1],
|
||||
["degrees16", NetworkSchemas.degrees16(), 127. / 255. * 360., 2],
|
||||
["degrees32", NetworkSchemas.degrees32(), 127. / 255. * 360., 4],
|
||||
|
||||
["radians8", NetworkSchemas.radians8(), 127. / 255. * TAU, 1],
|
||||
["radians16", NetworkSchemas.radians16(), 127. / 255. * TAU, 2],
|
||||
["radians32", NetworkSchemas.radians32(), 127. / 255. * TAU, 4],
|
||||
|
||||
["float16", NetworkSchemas.float16(), 2.0, 2 if has_half else 4],
|
||||
["float32", NetworkSchemas.float32(), 2.0, 4],
|
||||
["float64", NetworkSchemas.float64(), 2.0, 8],
|
||||
|
||||
["vec2f16", NetworkSchemas.vec2f32(), Vector2(+1, -1), 4 if has_half else 8],
|
||||
["vec2f32", NetworkSchemas.vec2f32(), Vector2(+1, -1), 8],
|
||||
["vec2f64", NetworkSchemas.vec2f64(), Vector2(+1, -1), 16],
|
||||
["vec3f32", NetworkSchemas.vec3f32(), Vector3(+1, -1, .5), 12],
|
||||
["vec3f64", NetworkSchemas.vec3f64(), Vector3(+1, -1, .5), 24],
|
||||
["vec4f32", NetworkSchemas.vec4f32(), Vector4(+1, -1, .5, -5), 16],
|
||||
["vec4f64", NetworkSchemas.vec4f64(), Vector4(+1, -1, .5, -5), 32],
|
||||
|
||||
["normal2f16", NetworkSchemas.normal2f16(), Vector2.RIGHT.rotated(PI / 6.), 2 if has_half else 4],
|
||||
["normal2f32", NetworkSchemas.normal2f32(), Vector2.RIGHT.rotated(PI / 6.), 4],
|
||||
["normal2f64", NetworkSchemas.normal2f64(), Vector2.RIGHT.rotated(PI / 6.), 8],
|
||||
["normal3f16", NetworkSchemas.normal3f16(), Vector3.UP, 4 if has_half else 8],
|
||||
["normal3f32", NetworkSchemas.normal3f32(), Vector3.UP, 8],
|
||||
["normal3f64", NetworkSchemas.normal3f64(), Vector3.UP, 16],
|
||||
|
||||
["quat16f", NetworkSchemas.quatf16(), Quaternion.from_euler(Vector3.ONE), 8 if has_half else 16],
|
||||
["quat32f", NetworkSchemas.quatf32(), Quaternion.from_euler(Vector3.ONE), 16],
|
||||
["quat64f", NetworkSchemas.quatf64(), Quaternion.from_euler(Vector3.ONE), 32],
|
||||
|
||||
["transform2f16", NetworkSchemas.transform2f16(), Transform2D.IDENTITY.rotated(37.), 12 if has_half else 24],
|
||||
["transform2f32", NetworkSchemas.transform2f32(), Transform2D.IDENTITY.rotated(37.), 24],
|
||||
["transform2f64", NetworkSchemas.transform2f64(), Transform2D.IDENTITY.rotated(37.), 48],
|
||||
["transform3f16", NetworkSchemas.transform3f16(), Transform3D.IDENTITY.rotated(Vector3.ONE, 37.), 24 if has_half else 48],
|
||||
["transform3f32", NetworkSchemas.transform3f32(), Transform3D.IDENTITY.rotated(Vector3.ONE, 37.), 48],
|
||||
["transform3f64", NetworkSchemas.transform3f64(), Transform3D.IDENTITY.rotated(Vector3.ONE, 37.), 96],
|
||||
|
||||
["array", NetworkSchemas.array_of(NetworkSchemas.uint16()), [1, 2, 3], 8],
|
||||
["dictionary", NetworkSchemas.dictionary(NetworkSchemas.uint16(), NetworkSchemas.uint16()), { 1: 32, 2: 48 }, 10],
|
||||
|
||||
["netref id8", NetworkSchemas._netref(), _NetworkIdentityReference.of_id(12), 1],
|
||||
["netref id16", NetworkSchemas._netref(), _NetworkIdentityReference.of_id(138), 2],
|
||||
["netref name", NetworkSchemas._netref(), _NetworkIdentityReference.of_full_name("path"), 9],
|
||||
|
||||
# Private
|
||||
["varbits", NetworkSchemas._varbits(), _Bitset.of_bools([0, 1, 0, 1, 1, 0, 1]), 1]
|
||||
]
|
||||
|
||||
for case in cases:
|
||||
var name := case[0] as String
|
||||
var serializer := case[1] as NetworkSchemaSerializer
|
||||
var value = case[2]
|
||||
var expected_size := case[3] as int
|
||||
|
||||
test(name, func():
|
||||
var buffer := StreamPeerBuffer.new()
|
||||
serializer.encode(value, buffer)
|
||||
buffer.seek(0)
|
||||
var decoded = serializer.decode(buffer)
|
||||
|
||||
expect_equal(decoded, value, "Value mismatch! Expected %s, got %s" % [value, decoded])
|
||||
expect_equal(buffer.data_array.size(), expected_size, "Size mismatch! Expected %d, got %d" % [expected_size, buffer.data_array.size(), expected_size])
|
||||
)
|
||||
|
||||
test("should handle negative degrees", func():
|
||||
var input_value := -60.
|
||||
var expected_value := 300.
|
||||
var threshold := 1.
|
||||
|
||||
var buffer := StreamPeerBuffer.new()
|
||||
var serializer := NetworkSchemas.degrees16()
|
||||
serializer.encode(input_value, buffer)
|
||||
|
||||
buffer.seek(0)
|
||||
var decoded := serializer.decode(buffer) as float
|
||||
|
||||
expect(abs(decoded - expected_value) < threshold, "Decoded %s while expected %s!" % [decoded, expected_value])
|
||||
Vest.message("Difference: %s" % [abs(decoded - expected_value)])
|
||||
)
|
||||
|
||||
test("should handle negative radians", func():
|
||||
var input_value := -TAU / 6.
|
||||
var expected_value := 5. / 6. * TAU
|
||||
var threshold := 1. / TAU
|
||||
|
||||
var buffer := StreamPeerBuffer.new()
|
||||
var serializer := NetworkSchemas.radians16()
|
||||
serializer.encode(input_value, buffer)
|
||||
|
||||
buffer.seek(0)
|
||||
var decoded := serializer.decode(buffer) as float
|
||||
|
||||
expect(abs(decoded - expected_value) < threshold, "Decoded %s while expected %s!" % [decoded, expected_value])
|
||||
Vest.message("Difference: %s" % [abs(decoded - expected_value)])
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bk7cc3vjakruu
|
||||
@@ -0,0 +1,102 @@
|
||||
extends SnapshotSerializerTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "DenseSnapshotSerializer"
|
||||
|
||||
func suite() -> void:
|
||||
test("should deserialize to same", func():
|
||||
var schema := _NetworkSchema.new()
|
||||
var serializer := _DenseSnapshotSerializer.new(schema)
|
||||
|
||||
var subject := await get_subject()
|
||||
NetworkIdentityServer.register_node(subject)
|
||||
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[subject, "position", Vector3.ZERO],
|
||||
[subject, "quaternion", Quaternion.from_euler(Vector3.ONE)],
|
||||
[subject, "scale", Vector3.ONE]
|
||||
], [subject])
|
||||
|
||||
var props := _PropertyPool.of([
|
||||
[subject, "position"],
|
||||
[subject, "quaternion"],
|
||||
[subject, "scale"]
|
||||
])
|
||||
|
||||
var serialized := serializer.write_for(1, snapshot, props)
|
||||
var deserialized := serializer.read_from(1, props, to_buffer(serialized))
|
||||
|
||||
expect_equal(deserialized, snapshot)
|
||||
|
||||
Vest.message("Serialized %d props to %d bytes" % [snapshot.size(), serialized.size()])
|
||||
)
|
||||
|
||||
test("should ignore unknown identifiers", func():
|
||||
var schema := _NetworkSchema.new()
|
||||
var writer_identity_server := _NetworkIdentityServer.new(TestingCommandServer.new())
|
||||
var reader_identity_server := _NetworkIdentityServer.new(TestingCommandServer.new())
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(writer_identity_server)
|
||||
Vest.get_tree().root.add_child.call_deferred(reader_identity_server)
|
||||
await writer_identity_server.ready; await reader_identity_server.ready
|
||||
|
||||
var writer_serializer := _DenseSnapshotSerializer.new(schema, writer_identity_server)
|
||||
var reader_serializer := _DenseSnapshotSerializer.new(schema, reader_identity_server)
|
||||
|
||||
var known_subject := await get_subject()
|
||||
var unknown_subject := await get_subject()
|
||||
|
||||
var writer_props := _PropertyPool.of([[known_subject, "position"], [unknown_subject, "position"]])
|
||||
var reader_props := _PropertyPool.of([[known_subject, "position"]])
|
||||
|
||||
writer_identity_server.register_node(known_subject)
|
||||
writer_identity_server.register_node(unknown_subject)
|
||||
|
||||
reader_identity_server.register_node(known_subject)
|
||||
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[unknown_subject, "position", Vector3.ZERO],
|
||||
[known_subject, "position", Vector3.ZERO]
|
||||
], [known_subject, unknown_subject])
|
||||
|
||||
var expected := _Snapshot.of(0, [
|
||||
[known_subject, "position", Vector3.ZERO]
|
||||
], [known_subject])
|
||||
|
||||
var serialized := writer_serializer.write_for(1, snapshot, writer_props)
|
||||
var deserialized := reader_serializer.read_from(1, reader_props, to_buffer(serialized))
|
||||
|
||||
expect_equal(deserialized, expected)
|
||||
)
|
||||
|
||||
test("should handle missing data", func():
|
||||
var schema := _NetworkSchema.new()
|
||||
var serializer := _DenseSnapshotSerializer.new(schema)
|
||||
|
||||
var subject := await get_subject()
|
||||
NetworkIdentityServer.register_node(subject)
|
||||
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[subject, "position", Vector3.ZERO],
|
||||
[subject, "quaternion", Quaternion.from_euler(Vector3.ONE)],
|
||||
[subject, "scale", Vector3.ONE]
|
||||
], [subject])
|
||||
|
||||
var props := _PropertyPool.of([
|
||||
[subject, "position"],
|
||||
[subject, "quaternion"],
|
||||
[subject, "scale"]
|
||||
])
|
||||
|
||||
var expected := _Snapshot.of(0, [
|
||||
[subject, "position", Vector3.ZERO],
|
||||
[subject, "quaternion", Quaternion.from_euler(Vector3.ONE)],
|
||||
[subject, "scale", null]
|
||||
], [subject])
|
||||
|
||||
var serialized := serializer.write_for(1, snapshot, props)
|
||||
serialized = serialized.slice(0, serialized.size() - 4)
|
||||
var deserialized := serializer.read_from(1, props, to_buffer(serialized))
|
||||
|
||||
expect_equal(deserialized, expected)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cluy2t885ych6
|
||||
@@ -0,0 +1,43 @@
|
||||
extends SnapshotSerializerTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "RedundantSnapshotSerializer"
|
||||
|
||||
func suite() -> void:
|
||||
test("should deserialize to same", func():
|
||||
var schema := _NetworkSchema.new()
|
||||
var serializer := _RedundantSnapshotSerializer.new(schema)
|
||||
|
||||
var subject := Node3D.new()
|
||||
Vest.get_tree().root.add_child.call_deferred(subject)
|
||||
await subject.ready
|
||||
NetworkIdentityServer.register_node(subject)
|
||||
|
||||
var snapshots := [
|
||||
_Snapshot.of(0, [
|
||||
[subject, "position", Vector3(0., 0., 0.)],
|
||||
[subject, "quaternion", Quaternion.from_euler(Vector3.ONE)],
|
||||
[subject, "scale", Vector3(1., 1., 1.)]
|
||||
], [subject]),
|
||||
_Snapshot.of(1, [
|
||||
[subject, "position", Vector3(1., 0., 0.)],
|
||||
[subject, "quaternion", Quaternion.from_euler(Vector3.ZERO)],
|
||||
[subject, "scale", Vector3(1., .5, 1.)]
|
||||
], [subject])
|
||||
] as Array[_Snapshot]
|
||||
|
||||
var props := _PropertyPool.of([
|
||||
[subject, "position"],
|
||||
[subject, "quaternion"],
|
||||
[subject, "scale"]
|
||||
])
|
||||
|
||||
var serialized := serializer.write_for(1, snapshots, props)
|
||||
var deserialized := serializer.read_from(1, props, to_buffer(serialized))
|
||||
|
||||
expect_equal(deserialized.size(), snapshots.size(), "Not all snapshots were deserialized!")
|
||||
for i in snapshots.size():
|
||||
expect_equal(deserialized[i], snapshots[i])
|
||||
|
||||
Vest.message("Serialized %d snapshots to %d bytes" % [snapshots.size(), serialized.size()])
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8k4gyko3nrqj
|
||||
@@ -0,0 +1,19 @@
|
||||
extends VestTest
|
||||
class_name SnapshotSerializerTest
|
||||
|
||||
func before_case(__):
|
||||
# Makes sure local peer is 1, otherwise identifiers get random local IDs
|
||||
Vest.get_tree().root.multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
|
||||
func to_buffer(data: PackedByteArray) -> StreamPeerBuffer:
|
||||
var buffer := StreamPeerBuffer.new()
|
||||
buffer.data_array = data
|
||||
return buffer
|
||||
|
||||
func get_subject() -> Node3D:
|
||||
var subject := Node3D.new()
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(subject)
|
||||
await subject.ready
|
||||
|
||||
return subject
|
||||
@@ -0,0 +1 @@
|
||||
uid://drf3txqsw1vlv
|
||||
@@ -0,0 +1,72 @@
|
||||
extends SnapshotSerializerTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "SparseSnapshotSerializer"
|
||||
|
||||
func suite() -> void:
|
||||
test("should deserialize to same", func():
|
||||
var schema := _NetworkSchema.new()
|
||||
var serializer := _SparseSnapshotSerializer.new(schema)
|
||||
|
||||
var subject := Node3D.new()
|
||||
Vest.get_tree().root.add_child.call_deferred(subject)
|
||||
await subject.ready
|
||||
NetworkIdentityServer.register_node(subject)
|
||||
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[subject, "position", Vector3.ZERO],
|
||||
[subject, "quaternion", Quaternion.from_euler(Vector3.ONE)],
|
||||
[subject, "scale", Vector3.ONE]
|
||||
], [subject])
|
||||
|
||||
var props := _PropertyPool.of([
|
||||
[subject, "position"],
|
||||
[subject, "quaternion"],
|
||||
[subject, "scale"]
|
||||
])
|
||||
|
||||
var serialized := serializer.write_for(1, snapshot, props)
|
||||
var deserialized := serializer.read_from(1, props, to_buffer(serialized))
|
||||
|
||||
expect_equal(deserialized, snapshot)
|
||||
|
||||
Vest.message("Serialized %d props to %d bytes" % [snapshot.size(), serialized.size()])
|
||||
)
|
||||
|
||||
test("should ignore unknown identifiers", func():
|
||||
var schema := _NetworkSchema.new()
|
||||
var writer_identity_server := _NetworkIdentityServer.new(TestingCommandServer.new())
|
||||
var reader_identity_server := _NetworkIdentityServer.new(TestingCommandServer.new())
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(writer_identity_server)
|
||||
Vest.get_tree().root.add_child.call_deferred(reader_identity_server)
|
||||
await writer_identity_server.ready; await reader_identity_server.ready
|
||||
|
||||
var writer_serializer := _SparseSnapshotSerializer.new(schema, writer_identity_server)
|
||||
var reader_serializer := _SparseSnapshotSerializer.new(schema, reader_identity_server)
|
||||
|
||||
var known_subject := await get_subject()
|
||||
var unknown_subject := await get_subject()
|
||||
|
||||
var writer_props := _PropertyPool.of([[known_subject, "position"], [unknown_subject, "position"]])
|
||||
var reader_props := _PropertyPool.of([[known_subject, "position"]])
|
||||
|
||||
writer_identity_server.register_node(known_subject)
|
||||
writer_identity_server.register_node(unknown_subject)
|
||||
|
||||
reader_identity_server.register_node(known_subject)
|
||||
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[unknown_subject, "position", Vector3.ZERO],
|
||||
[known_subject, "position", Vector3.ZERO]
|
||||
], [known_subject, unknown_subject])
|
||||
|
||||
var expected := _Snapshot.of(0, [
|
||||
[known_subject, "position", Vector3.ZERO]
|
||||
], [known_subject])
|
||||
|
||||
var serialized := writer_serializer.write_for(1, snapshot, writer_props)
|
||||
var deserialized := reader_serializer.read_from(1, reader_props, to_buffer(serialized))
|
||||
|
||||
expect_equal(deserialized, expected)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://2l21sx6aw4ek
|
||||
@@ -0,0 +1,72 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "TicksetSerializer"
|
||||
|
||||
func test_should_serialize_empty():
|
||||
# Given
|
||||
var earliest_tick = 15
|
||||
var latest_tick = 35
|
||||
var active_ticks = _Set.new()
|
||||
|
||||
# When
|
||||
var bytes = _TicksetSerializer.serialize(earliest_tick, latest_tick, active_ticks)
|
||||
var deserialized = _TicksetSerializer.deserialize(bytes)
|
||||
|
||||
# Then
|
||||
expect_equal(deserialized[0], earliest_tick)
|
||||
expect_equal(deserialized[1], latest_tick)
|
||||
expect_equal(deserialized[2], active_ticks)
|
||||
|
||||
func test_should_serialize_ticks():
|
||||
# Given
|
||||
var earliest_tick = 15
|
||||
var latest_tick = 35
|
||||
var active_ticks = _Set.of([15, 16, 20, 24])
|
||||
|
||||
# When
|
||||
var bytes = _TicksetSerializer.serialize(earliest_tick, latest_tick, active_ticks)
|
||||
var deserialized = _TicksetSerializer.deserialize(bytes)
|
||||
|
||||
# Then
|
||||
expect_equal(deserialized[0], earliest_tick)
|
||||
expect_equal(deserialized[1], latest_tick)
|
||||
expect_equal(deserialized[2], active_ticks)
|
||||
|
||||
func test_should_serialize_only_range():
|
||||
# Given
|
||||
var earliest_tick = 16
|
||||
var latest_tick = 20
|
||||
var active_ticks = _Set.of([15, 16, 20, 24])
|
||||
|
||||
# When
|
||||
var bytes = _TicksetSerializer.serialize(earliest_tick, latest_tick, active_ticks)
|
||||
var deserialized = _TicksetSerializer.deserialize(bytes)
|
||||
|
||||
# Then
|
||||
expect_equal(deserialized[0], earliest_tick)
|
||||
expect_equal(deserialized[1], latest_tick)
|
||||
expect_equal(deserialized[2], _Set.of([16, 20]))
|
||||
|
||||
func test_should_fail_on_earliest_after_latest():
|
||||
# Given
|
||||
var earliest_tick = 42
|
||||
var latest_tick = 12
|
||||
var active_ticks = _Set.new()
|
||||
|
||||
# When
|
||||
var bytes = _TicksetSerializer.serialize(earliest_tick, latest_tick, active_ticks)
|
||||
|
||||
expect(bytes.is_empty())
|
||||
|
||||
func test_bandwidth():
|
||||
var earliest_tick = 15
|
||||
var latest_tick = 35
|
||||
var active_ticks = _Set.of([15, 16, 20, 24])
|
||||
|
||||
var empty_buffer := _TicksetSerializer.serialize(earliest_tick, latest_tick, _Set.of([]))
|
||||
var buffer := _TicksetSerializer.serialize(earliest_tick, latest_tick, active_ticks)
|
||||
|
||||
Vest.message("Data usage with empty tickset: %d bytes" % [empty_buffer.size()])
|
||||
Vest.message("Data usage with %d active ticks: %d bytes" % [active_ticks.size(), buffer.size()])
|
||||
ok()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bd0su60kc3cyu
|
||||
@@ -0,0 +1,77 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "Snapshot"
|
||||
|
||||
func suite() -> void:
|
||||
test("apply()", func():
|
||||
# 22.46us, 367.25us, 1.44ms, 2.90ms, 54.68ms
|
||||
# 23.92us, 331.23us, 1.35ms, 2.73ms, 45.37ms
|
||||
for case in [[16, 2, 64], [128, 4, 8], [512, 4, 4], [1024, 4, 1], [16384, 4, 1]]:
|
||||
var node_count := case[0] as int
|
||||
var prop_count := case[1] as int
|
||||
var batch_size := case[2] as int
|
||||
|
||||
var nodes := get_nodes(node_count)
|
||||
|
||||
var snapshot := _Snapshot.new(0)
|
||||
for node in nodes:
|
||||
for i in prop_count:
|
||||
var prop := "property%d" % (i + 1)
|
||||
snapshot.record_property(node, prop)
|
||||
|
||||
benchmark("apply() - %dn/%dp" % [node_count, prop_count], func(__):
|
||||
snapshot.apply()
|
||||
).with_duration(1.).with_batch_size(batch_size).run()
|
||||
|
||||
free_nodes(nodes)
|
||||
)
|
||||
|
||||
test("merge()", func():
|
||||
# 30.35us, 502.14us, 2.09ms, 4.85ms, 114.49ms
|
||||
# 20.75us, 178.53us, 704.67us, 1.46ms, 31.61ms
|
||||
for case in [[16, 2, 64], [128, 4, 8], [512, 4, 4], [1024, 4, 1], [16384, 4, 1]]:
|
||||
var node_count := case[0] as int
|
||||
var prop_count := case[1] as int
|
||||
var batch_size := case[2] as int
|
||||
|
||||
var nodes := get_nodes(node_count)
|
||||
|
||||
var base_snapshot := _Snapshot.new(0)
|
||||
var patch_snapshot := _Snapshot.new(0)
|
||||
|
||||
for node in nodes:
|
||||
for i in prop_count:
|
||||
var prop := "property%d" % (i + 1)
|
||||
base_snapshot.set_property(node, prop, randi())
|
||||
patch_snapshot.set_property(node, prop, randi())
|
||||
|
||||
benchmark("merge() - %dn/%dp" % [node_count, prop_count], func(__):
|
||||
base_snapshot.merge(patch_snapshot)
|
||||
).with_duration(1.).with_batch_size(batch_size).run()
|
||||
|
||||
free_nodes(nodes)
|
||||
)
|
||||
|
||||
func get_nodes(count: int) -> Array[TestNode]:
|
||||
var nodes := [] as Array[TestNode]
|
||||
for i in count:
|
||||
var node := TestNode.new()
|
||||
node.name += " %d" % i
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
func free_nodes(nodes: Array[TestNode]) -> void:
|
||||
for node in nodes:
|
||||
node.queue_free()
|
||||
|
||||
class TestNode extends Node:
|
||||
var property1 := randi()
|
||||
var property2 := randi()
|
||||
var property3 := randi()
|
||||
var property4 := randi()
|
||||
|
||||
var property5 := randi()
|
||||
var property6 := randi()
|
||||
var property7 := randi()
|
||||
var property8 := randi()
|
||||
@@ -0,0 +1 @@
|
||||
uid://do3iolh8ysmcb
|
||||
@@ -0,0 +1,147 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "Snapshot"
|
||||
|
||||
func suite() -> void:
|
||||
var node := Node3D.new()
|
||||
var other_node := Node3D.new()
|
||||
|
||||
define("make_patch()", func():
|
||||
test("should return empty on same", func():
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[node, "scale", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
expect_empty(_Snapshot.make_patch(snapshot, snapshot))
|
||||
)
|
||||
|
||||
test("should include differing property", func():
|
||||
var from := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[node, "scale", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
var to := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[node, "scale", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
var expected := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
expect_equal(_Snapshot.make_patch(from, to), expected)
|
||||
)
|
||||
|
||||
test("should include new property", func():
|
||||
var from := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO]
|
||||
], [node])
|
||||
|
||||
var to := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[node, "scale", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
var expected := _Snapshot.of(0, [
|
||||
[node, "scale", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
expect_equal(_Snapshot.make_patch(from, to), expected)
|
||||
)
|
||||
|
||||
test("patch should yield `to` on merge", func():
|
||||
var from := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[node, "scale", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
var to := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[node, "scale", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
var patch := _Snapshot.make_patch(from, to)
|
||||
var applied := from.duplicate()
|
||||
applied.tick = to.tick
|
||||
applied.merge(patch)
|
||||
|
||||
expect_equal(applied, to)
|
||||
)
|
||||
)
|
||||
|
||||
define("merge()", func():
|
||||
test("auth should override non-auth", func():
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[other_node, "position", Vector3.ZERO]
|
||||
], [])
|
||||
|
||||
var patch := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
expect_true(snapshot.merge(patch))
|
||||
expect_equal(snapshot, _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], [node]))
|
||||
)
|
||||
|
||||
test("auth should update auth", func():
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[other_node, "position", Vector3.ZERO]
|
||||
], [node])
|
||||
|
||||
var patch := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], [node])
|
||||
|
||||
expect_true(snapshot.merge(patch))
|
||||
expect_equal(snapshot, _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], [node]))
|
||||
)
|
||||
|
||||
test("non-auth should not update auth", func():
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[other_node, "position", Vector3.ZERO]
|
||||
], [node])
|
||||
|
||||
var patch := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], [])
|
||||
|
||||
expect_true(snapshot.merge(patch))
|
||||
expect_equal(snapshot, _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], [node]))
|
||||
)
|
||||
|
||||
test("non-auth should update non-auth", func():
|
||||
var snapshot := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ZERO],
|
||||
[other_node, "position", Vector3.ZERO]
|
||||
], [])
|
||||
|
||||
var patch := _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], [])
|
||||
|
||||
expect_true(snapshot.merge(patch))
|
||||
expect_equal(snapshot, _Snapshot.of(0, [
|
||||
[node, "position", Vector3.ONE],
|
||||
[other_node, "position", Vector3.ONE]
|
||||
], []))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://csptcun1bgc5d
|
||||
@@ -0,0 +1,61 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "NetworkHistoryServer"
|
||||
|
||||
var idx := 0
|
||||
|
||||
func suite():
|
||||
for count in [16, 1024, 16384]:
|
||||
# register / record / restore / deregister
|
||||
# *2.06us/235.37us/*46.01us/*1.37us, 14.05us/14.73ms/1.08ms/6.56us, 207.84us/234.73ms/23.18ms/88.84us
|
||||
# *2.19us/77.97us/*23.46us/*3.07us, 1.73us/ 4.55ms/1.83ms/1.48us, 2.15us/ 81.64ms/45.45ms/ 1.57us
|
||||
test("%d nodes" % count, func():
|
||||
# Create nodes
|
||||
var nodes := await get_nodes(count)
|
||||
|
||||
# Register nodes
|
||||
idx = 0
|
||||
benchmark("register()", func(__):
|
||||
NetworkHistoryServer.register_rollback_state(nodes[idx], "name")
|
||||
idx += 1
|
||||
).with_iterations(count).with_batch_size(count).run()
|
||||
|
||||
# Record
|
||||
benchmark("record()", func(__):
|
||||
NetworkHistoryServer._record_rollback_state(0)
|
||||
).with_duration(1.).with_batch_size(16).run()
|
||||
|
||||
# Restore
|
||||
benchmark("restore()", func(__):
|
||||
NetworkHistoryServer._restore_rollback_state(0)
|
||||
).with_duration(1.).with_batch_size(16).run()
|
||||
|
||||
# Deregister nodes
|
||||
idx = 0
|
||||
benchmark("deregister()", func(__):
|
||||
NetworkHistoryServer.deregister_rollback_state(nodes[idx], "name")
|
||||
idx += 1
|
||||
).with_iterations(count).with_batch_size(count).run()
|
||||
|
||||
# Free nodes
|
||||
free_nodes(nodes)
|
||||
)
|
||||
|
||||
func get_nodes(count: int) -> Array[Node]:
|
||||
var nodes := [] as Array[Node]
|
||||
|
||||
for i in count:
|
||||
var node := Node.new()
|
||||
node.name = "Node %d" % i
|
||||
nodes.append(node)
|
||||
Vest.get_tree().root.add_child.call_deferred(node)
|
||||
|
||||
for node in nodes:
|
||||
await node.ready
|
||||
|
||||
return nodes
|
||||
|
||||
func free_nodes(nodes: Array[Node]) -> void:
|
||||
for node in nodes:
|
||||
node.queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bygji0wruckul
|
||||
@@ -0,0 +1,66 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "NetworkIdentityServer"
|
||||
|
||||
var command_server: _NetworkCommandServer
|
||||
var identity_server: _NetworkIdentityServer
|
||||
|
||||
func before_case(__):
|
||||
# Makes sure local peer is 1, otherwise identifiers get random local IDs
|
||||
Vest.get_tree().root.multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
|
||||
command_server = _NetworkCommandServer.new()
|
||||
identity_server = _NetworkIdentityServer.new(command_server)
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(command_server)
|
||||
Vest.get_tree().root.add_child.call_deferred(identity_server)
|
||||
|
||||
await command_server.ready
|
||||
await identity_server.ready
|
||||
|
||||
func after_case(__):
|
||||
identity_server.queue_free()
|
||||
command_server.queue_free()
|
||||
|
||||
func suite() -> void:
|
||||
define("resolve_reference()", func():
|
||||
# 6.04us/7.08us, 26.75us/32.29us, 211.05us/240.66us, 4.55ms/4.35ms
|
||||
# ??/3.69us, ??/3.74us, ??/3.81us, ??/3.99us
|
||||
# 2.73us/3.63us, 2.73us/3.72us, 2.68us/3.68us, 2.91us/3.95us
|
||||
for case in [[16, 512], [128, 512], [1024, 512], [16384, 512]]:
|
||||
var count := case[0] as int
|
||||
var batch := case[1] as int
|
||||
|
||||
# Run benchmarks
|
||||
test("resolve %d nodes" % count, func():
|
||||
# Setup nodes
|
||||
var nodes := [] as Array[Node]
|
||||
for i in count:
|
||||
var node := Node.new()
|
||||
node.name = "Node %d" % i
|
||||
nodes.append(node)
|
||||
Vest.get_tree().root.add_child.call_deferred(node)
|
||||
|
||||
# Wait for nodes to be in tree and register
|
||||
for node in nodes:
|
||||
await node.ready
|
||||
identity_server.register_node(node)
|
||||
|
||||
benchmark("resolve by ID", func(__):
|
||||
var id := (randi() % count) as int
|
||||
var reference := _NetworkIdentityReference.of_id(id)
|
||||
identity_server._resolve_reference(1, reference, false)
|
||||
).with_batch_size(batch).with_duration(1.).run()
|
||||
|
||||
benchmark("resolve by name", func(__):
|
||||
var id := (randi() % count) as int
|
||||
var reference := _NetworkIdentityReference.of_full_name("/root/Node %d" % id)
|
||||
identity_server._resolve_reference(1, reference, false)
|
||||
).with_batch_size(batch).with_duration(1.).run()
|
||||
|
||||
# Free nodes
|
||||
for node in nodes:
|
||||
node.queue_free()
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cs0iapbvn2jta
|
||||
@@ -0,0 +1,128 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "NetworkIdentityServer"
|
||||
|
||||
var command_server: TestingCommandServer
|
||||
var identity_server: _NetworkIdentityServer
|
||||
|
||||
var node: Node
|
||||
var orphan_node: Node
|
||||
|
||||
func before_case(__):
|
||||
# Makes sure local peer is 1, otherwise identifiers get random local IDs
|
||||
Vest.get_tree().root.multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
|
||||
command_server = TestingCommandServer.new()
|
||||
identity_server = _NetworkIdentityServer.new(command_server)
|
||||
|
||||
node = Node.new()
|
||||
node.name = "Identified Node"
|
||||
|
||||
orphan_node = Node.new()
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(node)
|
||||
Vest.get_tree().root.add_child.call_deferred(command_server)
|
||||
Vest.get_tree().root.add_child.call_deferred(identity_server)
|
||||
|
||||
await node.ready
|
||||
await command_server.ready
|
||||
await identity_server.ready
|
||||
|
||||
func after_case(__):
|
||||
identity_server.queue_free()
|
||||
command_server.queue_free()
|
||||
node.queue_free()
|
||||
orphan_node.queue_free()
|
||||
|
||||
func suite() -> void:
|
||||
define("register_node()", func():
|
||||
test("should register", func():
|
||||
# Register node
|
||||
identity_server.register_node(node)
|
||||
|
||||
# Assert for identifier
|
||||
var identifier := identity_server._get_identifier_of(node)
|
||||
expect_not_null(identifier)
|
||||
expect_equal(identifier.get_full_name(), "/root/Identified Node")
|
||||
)
|
||||
|
||||
test("should fail on node node in tree", func():
|
||||
# Register node
|
||||
identity_server.register_node(orphan_node)
|
||||
|
||||
# Assert for identifier
|
||||
var identifier := identity_server._get_identifier_of(orphan_node)
|
||||
expect_null(identifier)
|
||||
)
|
||||
)
|
||||
|
||||
define("deregister_node()", func():
|
||||
test("should remove known", func():
|
||||
# Register node
|
||||
identity_server.register_node(node)
|
||||
expect_not_null(identity_server._get_identifier_of(node))
|
||||
|
||||
# Deregister
|
||||
identity_server.deregister_node(node)
|
||||
expect_null(identity_server._get_identifier_of(node))
|
||||
)
|
||||
|
||||
test("should do nothing on unknown", func():
|
||||
identity_server.deregister_node(orphan_node)
|
||||
expect_null(identity_server._get_identifier_of(orphan_node))
|
||||
)
|
||||
)
|
||||
|
||||
define("flush_queue()", func():
|
||||
test("should send ids", func():
|
||||
# Register node
|
||||
identity_server.register_node(node)
|
||||
|
||||
var identifier := identity_server._get_identifier_of(node)
|
||||
var full_name := identifier.get_full_name()
|
||||
|
||||
# Try and resolve some unknown identities
|
||||
identity_server._resolve_reference(2, identifier.reference_for(2))
|
||||
identity_server._resolve_reference(3, identifier.reference_for(3))
|
||||
|
||||
# Flush queue
|
||||
identity_server.flush_queue()
|
||||
|
||||
# Check commands sent
|
||||
expect_equal(command_server.commands_sent.size(), 2)
|
||||
for i in command_server.commands_sent.size():
|
||||
var command := command_server.commands_sent[i]
|
||||
|
||||
expect_equal(command[0], 0) # Command id
|
||||
expect_equal(command[2], 2 + i) # Peer
|
||||
expect_equal(command[3], MultiplayerPeer.TRANSFER_MODE_RELIABLE)
|
||||
)
|
||||
)
|
||||
|
||||
define("_resolve_reference()", func():
|
||||
test("should return by id", func():
|
||||
# Register node
|
||||
identity_server.register_node(node)
|
||||
var identifier := identity_server._get_identifier_of(node)
|
||||
|
||||
# Resolve
|
||||
var reference := _NetworkIdentityReference.of_id(identifier.get_local_id())
|
||||
expect_equal(identity_server._resolve_reference(1, reference), identifier)
|
||||
)
|
||||
|
||||
test("should return by name", func():
|
||||
# Register node
|
||||
identity_server.register_node(node)
|
||||
var identifier := identity_server._get_identifier_of(node)
|
||||
|
||||
# Resolve
|
||||
var reference := _NetworkIdentityReference.of_full_name(identifier.get_full_name())
|
||||
expect_equal(identity_server._resolve_reference(1, reference), identifier)
|
||||
)
|
||||
|
||||
test("should return null on unknown", func():
|
||||
var reference := _NetworkIdentityReference.of_full_name("Unknown Node")
|
||||
expect_null(identity_server._resolve_reference(1, reference))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://si105rtl84q6
|
||||
@@ -0,0 +1,58 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "NetworkSynchronizationServer"
|
||||
|
||||
var servers: TestingServers
|
||||
|
||||
func before_case(__):
|
||||
# Makes sure local peer is 1, otherwise identifiers get random local IDs
|
||||
Vest.get_tree().root.multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
servers = await TestingServers.create()
|
||||
|
||||
servers.synchronization_server()._rb_enable_input_broadcast = true # Force input broadcast
|
||||
|
||||
func after_case(__):
|
||||
servers.queue_free()
|
||||
|
||||
func suite() -> void:
|
||||
define("synchronize_input()", func():
|
||||
test("should submit owned", func():
|
||||
var owned_node := await get_node()
|
||||
var other_node := await get_node()
|
||||
|
||||
other_node.set_multiplayer_authority(2)
|
||||
|
||||
servers.history_server().register_rollback_input(owned_node, "position")
|
||||
servers.history_server().register_rollback_input(other_node, "position")
|
||||
|
||||
servers.synchronization_server().register_rollback_input(owned_node, "position")
|
||||
servers.synchronization_server().register_rollback_input(other_node, "position")
|
||||
|
||||
servers.history_server()._record_rollback_input(0)
|
||||
servers.synchronization_server()._synchronize_input(0)
|
||||
|
||||
skip() # Somehow setup a live client-server connection
|
||||
)
|
||||
)
|
||||
|
||||
define("synchronize_state()", func():
|
||||
test("should submit owned", func():
|
||||
skip()
|
||||
)
|
||||
)
|
||||
|
||||
define("synchronize_sync_state()", func():
|
||||
test("should submit owned", func():
|
||||
skip()
|
||||
)
|
||||
)
|
||||
|
||||
func get_node(name: String = "") -> Node3D:
|
||||
var node := Node3D.new()
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(node)
|
||||
await node.ready
|
||||
if name: node.name = name
|
||||
|
||||
return node
|
||||
@@ -0,0 +1 @@
|
||||
uid://csdkme0ex4dxl
|
||||
@@ -0,0 +1,114 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "RollbackSimulationServer"
|
||||
|
||||
func before_case(__):
|
||||
# Makes sure local peer is 1, otherwise identifiers get random local IDs
|
||||
Vest.get_tree().root.multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
|
||||
func suite() -> void:
|
||||
define("is_predicting()", func():
|
||||
test("should predict non-owned node", func():
|
||||
var state_node := await get_node()
|
||||
var input_node := await get_node()
|
||||
var input_snapshot := _Snapshot.of(1, [[input_node, "name", "Input"]], [])
|
||||
|
||||
state_node.set_multiplayer_authority(2)
|
||||
RollbackSimulationServer.register_rollback_input_for(state_node, input_node)
|
||||
|
||||
expect(RollbackSimulationServer._is_predicting(input_snapshot, state_node))
|
||||
)
|
||||
|
||||
test("should predict owned node without input", func():
|
||||
var input_snapshot := _Snapshot.new(0)
|
||||
var state_node := await get_node()
|
||||
var input_node := await get_node()
|
||||
|
||||
RollbackSimulationServer.register_rollback_input_for(state_node, input_node)
|
||||
|
||||
expect(RollbackSimulationServer._is_predicting(input_snapshot, state_node))
|
||||
)
|
||||
|
||||
test("should predict non-owned inputless", func():
|
||||
var input_snapshot := _Snapshot.new(0)
|
||||
var state_node := await get_node()
|
||||
|
||||
state_node.set_multiplayer_authority(2)
|
||||
|
||||
expect(RollbackSimulationServer._is_predicting(input_snapshot, state_node))
|
||||
)
|
||||
|
||||
test("should not predict owned inputless", func():
|
||||
var input_snapshot := _Snapshot.new(0)
|
||||
var state_node := await get_node()
|
||||
|
||||
expect_not(RollbackSimulationServer._is_predicting(input_snapshot, state_node))
|
||||
)
|
||||
|
||||
test("should not predict owned with input", func():
|
||||
var state_node := await get_node()
|
||||
var input_node := await get_node()
|
||||
var input_snapshot := _Snapshot.of(1, [[input_node, "name", "Input"]], [input_node])
|
||||
|
||||
RollbackSimulationServer.register_rollback_input_for(state_node, input_node)
|
||||
|
||||
expect_not(RollbackSimulationServer._is_predicting(input_snapshot, state_node))
|
||||
)
|
||||
)
|
||||
|
||||
define("get_nodes_to_simulate()", func():
|
||||
test("should not simulate without input", func():
|
||||
var node := RewindableNode.new()
|
||||
var input_node := Node.new()
|
||||
|
||||
var server := _RollbackSimulationServer.new()
|
||||
server.register(node._rollback_tick)
|
||||
server.register_rollback_input_for(node, input_node)
|
||||
|
||||
var snapshot := _Snapshot.new(1)
|
||||
|
||||
expect_empty(server._get_nodes_to_simulate(snapshot))
|
||||
)
|
||||
|
||||
test("should simulate with input", func():
|
||||
var node := RewindableNode.new()
|
||||
var input_node := Node.new()
|
||||
|
||||
var server := _RollbackSimulationServer.new()
|
||||
server.register(node._rollback_tick)
|
||||
server.register_rollback_input_for(node, input_node)
|
||||
|
||||
var snapshot := _Snapshot.new(1)
|
||||
snapshot.set_property(input_node, "editor_description", "Test input node")
|
||||
snapshot.set_auth(input_node, true)
|
||||
|
||||
expect_equal(server._get_nodes_to_simulate(snapshot), [node])
|
||||
)
|
||||
|
||||
test("should simulate mutated", func():
|
||||
var node := RewindableNode.new()
|
||||
var input_node := Node.new()
|
||||
|
||||
var server := _RollbackSimulationServer.new()
|
||||
server.register(node._rollback_tick)
|
||||
server.register_rollback_input_for(node, input_node)
|
||||
|
||||
var snapshot := _Snapshot.new(1)
|
||||
NetworkRollback.mutate(node)
|
||||
|
||||
expect_equal(server._get_nodes_to_simulate(snapshot), [node])
|
||||
)
|
||||
)
|
||||
|
||||
class RewindableNode extends Node:
|
||||
func _rollback_tick(_dt, _t, _if) -> void:
|
||||
pass
|
||||
|
||||
func get_node() -> Node:
|
||||
var node := Node.new()
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(node)
|
||||
await node.ready
|
||||
|
||||
return node
|
||||
@@ -0,0 +1 @@
|
||||
uid://bxv65630oy2jj
|
||||
@@ -0,0 +1,7 @@
|
||||
extends _NetworkCommandServer
|
||||
class_name TestingCommandServer
|
||||
|
||||
var commands_sent := [] as Array[Array]
|
||||
|
||||
func send_command(idx: int, data: PackedByteArray, target_peer: int = 0, mode: MultiplayerPeer.TransferMode = MultiplayerPeer.TRANSFER_MODE_RELIABLE, channel: int = 0) -> void:
|
||||
commands_sent.append([idx, data, target_peer, mode, channel])
|
||||
@@ -0,0 +1 @@
|
||||
uid://dch0o2dsb4bv1
|
||||
@@ -0,0 +1,52 @@
|
||||
extends Node
|
||||
class_name TestingServers
|
||||
|
||||
var _command_server: CommandServer
|
||||
var _identity_server: _NetworkIdentityServer
|
||||
var _history_server: _NetworkHistoryServer
|
||||
var _synchronization_server: _NetworkSynchronizationServer
|
||||
var _simulation_server: _RollbackSimulationServer
|
||||
|
||||
static func create() -> TestingServers:
|
||||
var servers := TestingServers.new()
|
||||
|
||||
Vest.get_tree().root.add_child.call_deferred(servers)
|
||||
await servers.ready
|
||||
|
||||
return servers
|
||||
|
||||
func _ready():
|
||||
_command_server = CommandServer.new()
|
||||
_history_server = _NetworkHistoryServer.new()
|
||||
_identity_server = _NetworkIdentityServer.new(_command_server)
|
||||
_synchronization_server = _NetworkSynchronizationServer.new(_command_server, _history_server, _identity_server, _simulation_server)
|
||||
_simulation_server = _RollbackSimulationServer.new(_history_server)
|
||||
var servers := [_command_server, _history_server, _identity_server, _synchronization_server, _simulation_server]
|
||||
|
||||
for server in servers:
|
||||
add_child.call_deferred(server)
|
||||
|
||||
for server in servers:
|
||||
await server.ready
|
||||
|
||||
func command_server() -> CommandServer:
|
||||
return _command_server
|
||||
|
||||
func identity_server() -> _NetworkIdentityServer:
|
||||
return _identity_server
|
||||
|
||||
func history_server() -> _NetworkHistoryServer:
|
||||
return _history_server
|
||||
|
||||
func synchronization_server() -> _NetworkSynchronizationServer:
|
||||
return _synchronization_server
|
||||
|
||||
func simulation_server() -> _RollbackSimulationServer:
|
||||
return _simulation_server
|
||||
|
||||
|
||||
class CommandServer extends _NetworkCommandServer:
|
||||
var commands_sent := [] as Array[Array]
|
||||
|
||||
func send_command(idx: int, data: PackedByteArray, target_peer: int = 0, mode: MultiplayerPeer.TransferMode = MultiplayerPeer.TRANSFER_MODE_RELIABLE, channel: int = 0) -> void:
|
||||
commands_sent.append([idx, data, target_peer, mode, channel])
|
||||
@@ -0,0 +1 @@
|
||||
uid://b0xnh4u80f7ux
|
||||
@@ -0,0 +1,69 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "NetworkTickrateHandshake"
|
||||
|
||||
func suite() -> void:
|
||||
var base_tickrate := 60
|
||||
var mismatching_tickrate := 48
|
||||
|
||||
on_case_begin.connect(func(__):
|
||||
# Reset tickrate
|
||||
NetworkMocks.set_tickrate(base_tickrate)
|
||||
)
|
||||
|
||||
test("client should disconnect on mismatch", func():
|
||||
# Difficult to pretend we're a client AND detect a disconnect
|
||||
todo()
|
||||
)
|
||||
|
||||
test("client should adjust on mismatch", func():
|
||||
var handshake := await get_handshake(false)
|
||||
handshake.mismatch_action = NetworkTickrateHandshake.ADJUST
|
||||
|
||||
handshake.submit_tickrate(mismatching_tickrate)
|
||||
expect_equal(NetworkTime.tickrate, mismatching_tickrate, "Tickrate was not adjusted!")
|
||||
)
|
||||
|
||||
test("server should not adjust on mismatch", func():
|
||||
var handshake := await get_handshake(true)
|
||||
handshake.mismatch_action = NetworkTickrateHandshake.ADJUST
|
||||
|
||||
handshake.submit_tickrate(mismatching_tickrate)
|
||||
expect_equal(NetworkTime.tickrate, base_tickrate, "Tickrate was adjusted!")
|
||||
)
|
||||
|
||||
test("client should emit signal on mismatch", func():
|
||||
var handshake := await get_handshake(false)
|
||||
handshake.mismatch_action = NetworkTickrateHandshake.SIGNAL
|
||||
capture_signal(handshake.on_tickrate_mismatch, 2)
|
||||
|
||||
handshake.submit_tickrate(mismatching_tickrate)
|
||||
|
||||
expect_equal(
|
||||
get_signal_emissions(handshake.on_tickrate_mismatch),
|
||||
[[0, mismatching_tickrate]]
|
||||
)
|
||||
)
|
||||
|
||||
func get_handshake(is_authority: bool) -> TestingTickrateHandshake:
|
||||
var handshake := TestingTickrateHandshake.new(is_authority)
|
||||
|
||||
# Add to tree
|
||||
# TODO(vest): Use Vest.get_tree() after upgrade
|
||||
Vest._scene_tree.root.add_child.call_deferred(handshake)
|
||||
await handshake.ready
|
||||
|
||||
return handshake
|
||||
|
||||
class TestingTickrateHandshake extends NetworkTickrateHandshake:
|
||||
var _is_tickrate_auth: bool
|
||||
|
||||
func _init(p_is_tickrate_auth: bool):
|
||||
_is_tickrate_auth = p_is_tickrate_auth
|
||||
|
||||
func submit_tickrate(tickrate: int):
|
||||
_submit_tickrate(tickrate)
|
||||
|
||||
func _is_authority() -> bool:
|
||||
return _is_tickrate_auth
|
||||
@@ -0,0 +1 @@
|
||||
uid://sjw7blpv81fr
|
||||
Reference in New Issue
Block a user