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,56 @@
|
||||
extends VestTest
|
||||
|
||||
const OneOffInput = preload("res://examples/snippets/input-gathering-tutorial/one-off-input.gd")
|
||||
|
||||
func get_suite_name():
|
||||
return "OneOffInput"
|
||||
|
||||
func suite():
|
||||
var action = "move_jump"
|
||||
var input := OneOffInput.new()
|
||||
|
||||
# TODO(vest): Some good way to add nodes to the tree
|
||||
await NetworkTime.get_tree().process_frame
|
||||
NetworkTime.get_tree().root.add_child(input)
|
||||
|
||||
test("should return false with no input", func():
|
||||
expect_false(input.is_jumping)
|
||||
)
|
||||
|
||||
test("should return true for a single tick", func():
|
||||
# Activate input
|
||||
Input.action_press(action)
|
||||
# Process
|
||||
input._process(1. / 60.)
|
||||
# Deactive input
|
||||
Input.action_release(action)
|
||||
|
||||
# Start tick loop
|
||||
NetworkMocks.in_network_tick_loop(func():
|
||||
# Single tick
|
||||
NetworkMocks.run_network_tick()
|
||||
|
||||
# Check for result
|
||||
expect_true(input.is_jumping)
|
||||
)
|
||||
)
|
||||
|
||||
test("should return true only on the first tick", func():
|
||||
# Activate input
|
||||
Input.action_press(action)
|
||||
# Process
|
||||
input._process(1. / 60.)
|
||||
# Deactive input
|
||||
Input.action_release(action)
|
||||
|
||||
# Start tick loop
|
||||
NetworkMocks.in_network_tick_loop(func():
|
||||
# First tick
|
||||
NetworkMocks.run_network_tick()
|
||||
expect_true(input.is_jumping, "First tick should have input!")
|
||||
|
||||
# Second tick
|
||||
NetworkMocks.run_network_tick()
|
||||
expect_false(input.is_jumping, "Second tick should not have input!")
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://e32thkcpm64x
|
||||
@@ -0,0 +1,35 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name():
|
||||
return "RewindableRandomNumberGenerator performance tests"
|
||||
|
||||
func test_benchmark() -> void:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
var rrng := RewindableRandomNumberGenerator.new(0)
|
||||
var sum := 0.0
|
||||
|
||||
benchmark("Built-in RNG", func(__):
|
||||
sum += rng.randfn()
|
||||
)\
|
||||
.with_duration(1.0)\
|
||||
.with_batch_size(4096)\
|
||||
.run()
|
||||
|
||||
NetworkMocks.in_rollback(func():
|
||||
NetworkMocks.set_tick(0, 0)
|
||||
|
||||
benchmark("Rewindable RNG on the same tick", func(__):
|
||||
sum += rrng.randfn()
|
||||
)\
|
||||
.with_duration(1.0)\
|
||||
.with_batch_size(4096)\
|
||||
.run()
|
||||
|
||||
benchmark("Rewindable RNG on the changing tick", func(__):
|
||||
NetworkRollback._tick += 1
|
||||
sum += rrng.randfn()
|
||||
)\
|
||||
.with_duration(1.0)\
|
||||
.with_batch_size(4096)\
|
||||
.run()
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://hedy5j0875h6
|
||||
@@ -0,0 +1,47 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name():
|
||||
return "RewindableRandomNumberGenerator"
|
||||
|
||||
func suite():
|
||||
test("should generate the same numbers for the same rollback tick in different loop", func():
|
||||
NetworkMocks.in_rollback(func():
|
||||
var rng := RewindableRandomNumberGenerator.new(0)
|
||||
|
||||
NetworkMocks.set_tick(2, 4)
|
||||
var first_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
NetworkMocks.set_tick(3, 4)
|
||||
var second_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
expect_equal(first_batch, second_batch)
|
||||
)
|
||||
)
|
||||
|
||||
test("should generate different numbers for different ticks", func():
|
||||
NetworkMocks.in_rollback(func():
|
||||
var rng := RewindableRandomNumberGenerator.new(0)
|
||||
|
||||
NetworkMocks.set_tick(2, 4)
|
||||
var first_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
NetworkMocks.set_tick(2, 2)
|
||||
var second_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
expect_not_equal(first_batch, second_batch)
|
||||
)
|
||||
)
|
||||
|
||||
test("different seeds should generate different numbers", func():
|
||||
NetworkMocks.in_rollback(func():
|
||||
NetworkMocks.set_tick(0, 0)
|
||||
|
||||
var first_rng := RewindableRandomNumberGenerator.new(1)
|
||||
var second_rng := RewindableRandomNumberGenerator.new(2)
|
||||
|
||||
var first_batch := range(4).map(func(__): return first_rng.randi_range(0, 10))
|
||||
var second_batch := range(4).map(func(__): return second_rng.randi_range(0, 10))
|
||||
|
||||
expect_not_equal(first_batch, second_batch)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8b8k164dxjsk
|
||||
@@ -0,0 +1,135 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "RewindableStateMachine"
|
||||
|
||||
var state_machine: RewindableStateMachine
|
||||
var first_state: RewindableState
|
||||
var other_state: RewindableState
|
||||
|
||||
func before_case(__):
|
||||
state_machine = RewindableStateMachine.new()
|
||||
first_state = mock(RewindableState)
|
||||
other_state = mock(RewindableState)
|
||||
|
||||
# Setup mock answers
|
||||
when(first_state.can_enter).then_return(true)
|
||||
when(first_state.enter).then_answer(func(__): pass)
|
||||
when(first_state.exit).then_answer(func(__): pass)
|
||||
when(first_state.tick).then_answer(func(__): pass)
|
||||
when(first_state.display_enter).then_answer(func(__): pass)
|
||||
when(first_state.display_exit).then_answer(func(__): pass)
|
||||
|
||||
when(other_state.can_enter).then_return(true)
|
||||
when(other_state.enter).then_answer(func(__): pass)
|
||||
when(other_state.exit).then_answer(func(__): pass)
|
||||
when(other_state.tick).then_answer(func(__): pass)
|
||||
when(other_state.display_enter).then_answer(func(__): pass)
|
||||
when(other_state.display_exit).then_answer(func(__): pass)
|
||||
|
||||
# Set state names
|
||||
first_state.name = "First State"
|
||||
other_state.name = "Other State"
|
||||
|
||||
# Add states as children - RSM will pick them up when added
|
||||
# **NOTE**: Make sure to set owner if spawning states manually, otherwise
|
||||
# RSM won't pick them up
|
||||
state_machine.add_child(first_state); first_state.owner = state_machine
|
||||
state_machine.add_child(other_state); other_state.owner = state_machine
|
||||
|
||||
# TODO(vest): TestingSceneTree
|
||||
await Vest._scene_tree.process_frame
|
||||
Vest._scene_tree.root.add_child(state_machine, true)
|
||||
|
||||
func test_should_start_empty():
|
||||
expect_empty(state_machine.state)
|
||||
|
||||
func test_should_notify_new_state_on_enter():
|
||||
capture_signal(first_state.on_enter, 3)
|
||||
|
||||
# Enter first state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Check for event
|
||||
expect_not_empty(get_signal_emissions(first_state.on_enter))
|
||||
expect_empty(get_calls_of(first_state.can_enter))
|
||||
expect_equal(get_calls_of(first_state.enter), [[null, 0]])
|
||||
|
||||
func test_on_enter_should_prevent_transition():
|
||||
other_state.on_enter.connect(
|
||||
func(_new_state, _tick, prevent):
|
||||
prevent.call()
|
||||
)
|
||||
|
||||
# Enter first state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Try to enter second state
|
||||
expect_false(state_machine.transition("Other State"), "Transition should have failed!")
|
||||
expect_equal(state_machine.state, "First State")
|
||||
|
||||
func test_on_exit_should_prevent_transition():
|
||||
first_state.on_exit.connect(
|
||||
func(_new_state, _tick, prevent):
|
||||
prevent.call()
|
||||
)
|
||||
|
||||
# Enter first state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Try to enter second state
|
||||
expect_false(state_machine.transition("Other State"), "Transition should have failed!")
|
||||
expect_equal(state_machine.state, "First State")
|
||||
|
||||
func test_can_enter_should_prevent_transition():
|
||||
state_machine.state = "First State"
|
||||
|
||||
# Register a more specific answer so the mock will use that
|
||||
when(other_state.can_enter)\
|
||||
.with_args([first_state])\
|
||||
.then_return(func(__): return false)
|
||||
|
||||
# Try to enter second state
|
||||
expect_false(state_machine.transition("Other State"), "Transition should have failed!")
|
||||
expect_equal(state_machine.state, "First State")
|
||||
|
||||
func test_should_call_tick():
|
||||
capture_signal(first_state.on_tick, 3)
|
||||
|
||||
# Set state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Run a rollback tick
|
||||
state_machine._rollback_tick(0.16, 0, true)
|
||||
|
||||
# Tick should have been called
|
||||
expect_equal(get_calls_of(first_state.tick), [[0.16, 0, true]], "Wrong method call!")
|
||||
expect_equal(get_signal_emissions(first_state.on_tick), [[0.16, 0, true]], "Wrong signal!")
|
||||
|
||||
func test_should_notify_display_state():
|
||||
state_machine.state = "First State"
|
||||
|
||||
capture_signal(first_state.on_display_enter, 2)
|
||||
capture_signal(first_state.on_display_exit, 2)
|
||||
capture_signal(other_state.on_display_enter, 2)
|
||||
|
||||
# First loop, display enters First State
|
||||
NetworkMocks.in_network_tick_loop(func():
|
||||
state_machine.transition("First State")
|
||||
)
|
||||
expect_not_empty(get_signal_emissions(first_state.on_display_enter))
|
||||
expect_not_empty(get_calls_of(first_state.display_enter))
|
||||
|
||||
# Second loop, display enters Other State
|
||||
NetworkMocks.in_network_tick_loop(func():
|
||||
state_machine.transition("Other State")
|
||||
)
|
||||
|
||||
expect_not_empty(get_signal_emissions(first_state.on_display_exit))
|
||||
expect_not_empty(get_signal_emissions(other_state.on_display_enter))
|
||||
|
||||
expect_not_empty(get_calls_of(first_state.display_exit))
|
||||
expect_not_empty(get_calls_of(other_state.display_enter))
|
||||
|
||||
func after_case(__):
|
||||
state_machine.queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cykemuvhqeq2r
|
||||
@@ -0,0 +1,59 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "BiMap"
|
||||
|
||||
func suite():
|
||||
test("should return by key", func():
|
||||
var bimap := _BiMap.new()
|
||||
bimap.put(1, "foo")
|
||||
bimap.put(2, "bar")
|
||||
|
||||
expect_equal(bimap.get_by_key(1), "foo")
|
||||
)
|
||||
|
||||
test("should return by value", func():
|
||||
var bimap := _BiMap.new()
|
||||
bimap.put(1, "foo")
|
||||
bimap.put(2, "bar")
|
||||
|
||||
expect_equal(bimap.get_by_value("foo"), 1)
|
||||
)
|
||||
|
||||
test("should return null on unknown key", func():
|
||||
var bimap := _BiMap.new()
|
||||
bimap.put(1, "foo")
|
||||
bimap.put(2, "bar")
|
||||
|
||||
expect_equal(bimap.get_by_key(3), null)
|
||||
)
|
||||
|
||||
test("should return null on unknown value", func():
|
||||
var bimap := _BiMap.new()
|
||||
bimap.put(1, "foo")
|
||||
bimap.put(2, "bar")
|
||||
|
||||
expect_equal(bimap.get_by_value("quix"), null)
|
||||
)
|
||||
|
||||
test("should rewrite on known key", func():
|
||||
var bimap := _BiMap.new()
|
||||
bimap.put(1, "foo")
|
||||
bimap.put(2, "bar")
|
||||
|
||||
bimap.put(1, "quix")
|
||||
|
||||
expect_equal(bimap.get_by_key(1), "quix")
|
||||
expect_equal(bimap.get_by_value("foo"), null)
|
||||
)
|
||||
|
||||
test("should rewrite on known value", func():
|
||||
var bimap := _BiMap.new()
|
||||
bimap.put(1, "foo")
|
||||
bimap.put(2, "bar")
|
||||
|
||||
bimap.put(1, "quix")
|
||||
|
||||
expect_equal(bimap.get_by_value("quix"), 1)
|
||||
expect_equal(bimap.get_by_value("foo"), null)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cmvjg7a72hjmg
|
||||
@@ -0,0 +1,40 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "Bitset"
|
||||
|
||||
func suite():
|
||||
test("should be empty on create", func():
|
||||
var bits := _Bitset.new(2)
|
||||
expect_false(bits.get_bit(0))
|
||||
expect_false(bits.get_bit(1))
|
||||
)
|
||||
|
||||
test("get_set_indices()", func():
|
||||
var bits := _Bitset.of_bools([0, 1, 1, 0])
|
||||
expect_equal(bits.get_set_indices(), [1, 2])
|
||||
)
|
||||
|
||||
test("set_bit()", func():
|
||||
var bits := _Bitset.new(4)
|
||||
var expected := _Bitset.of_bools([0, 1, 0, 1])
|
||||
bits.set_bit(1)
|
||||
bits.set_bit(3)
|
||||
expect_equal(bits, expected)
|
||||
)
|
||||
|
||||
test("clear_bit()", func():
|
||||
var bits := _Bitset.of_bools([0, 1, 1, 0])
|
||||
var expected := _Bitset.of_bools([0, 0, 1, 0])
|
||||
bits.clear_bit(1)
|
||||
bits.clear_bit(3)
|
||||
expect_equal(bits, expected)
|
||||
)
|
||||
|
||||
test("toggle_bit()", func():
|
||||
var bits := _Bitset.of_bools([0, 1, 1, 0])
|
||||
var expected := _Bitset.of_bools([1, 0, 1, 0])
|
||||
bits.toggle_bit(0)
|
||||
bits.toggle_bit(1)
|
||||
expect_equal(bits, expected)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bil8ugs8ol5h4
|
||||
@@ -0,0 +1,36 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "Graph"
|
||||
|
||||
func suite():
|
||||
var cases := [
|
||||
["tiny", 16, 2, 2048],
|
||||
["medium", 128, 4, 2048],
|
||||
["large", 1024, 16, 2048]
|
||||
]
|
||||
|
||||
for case in cases:
|
||||
var name := case[0] as String
|
||||
var items := case[1] as int
|
||||
var depth := case[2] as int
|
||||
var batch := case[3] as int
|
||||
|
||||
test("queries - %s graph, %d / %d" % [name, items, depth], func():
|
||||
var graph := _Graph.new()
|
||||
for i in items:
|
||||
for j in depth:
|
||||
graph.link(i, i + j)
|
||||
|
||||
benchmark("get_linked_from()", func(__):
|
||||
var nodes := graph.get_linked_from(randi() % items)
|
||||
).with_duration(1.).with_batch_size(batch).run()
|
||||
|
||||
benchmark("get_linked_to()", func(__):
|
||||
var nodes := graph.get_linked_to(randi() % items)
|
||||
).with_duration(1.).with_batch_size(batch).run()
|
||||
|
||||
benchmark("has_link()", func(__):
|
||||
var nodes := graph.has_link(randi() % items, randi() % items)
|
||||
).with_duration(1.).with_batch_size(batch).run()
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://brow8r8ya3xa0
|
||||
@@ -0,0 +1,48 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "Graph"
|
||||
|
||||
func suite():
|
||||
test("should add link", func():
|
||||
var graph := _Graph.new()
|
||||
graph.link("foo", "bar")
|
||||
|
||||
expect_linked(graph, "foo", "bar")
|
||||
expect_equal(graph.get_linked_from("foo"), ["bar"])
|
||||
expect_equal(graph.get_linked_to("bar"), ["foo"])
|
||||
)
|
||||
|
||||
test("should remove link", func():
|
||||
var graph := _Graph.new()
|
||||
graph.link("foo", "bar")
|
||||
graph.link("quix", "baz")
|
||||
|
||||
graph.unlink("foo", "bar")
|
||||
|
||||
expect_unlinked(graph, "foo", "bar")
|
||||
expect_linked(graph, "quix", "baz")
|
||||
expect_empty(graph.get_linked_from("foo"))
|
||||
expect_empty(graph.get_linked_to("bar"))
|
||||
)
|
||||
|
||||
test("should erase", func():
|
||||
var graph := _Graph.new()
|
||||
graph.link("foo", "bar")
|
||||
graph.link("foo", "baz")
|
||||
graph.link("quix", "baz")
|
||||
graph.link("oof", "foo")
|
||||
|
||||
graph.erase("foo")
|
||||
|
||||
expect_unlinked(graph, "foo", "bar")
|
||||
expect_unlinked(graph, "foo", "baz")
|
||||
expect_unlinked(graph, "oof", "foo")
|
||||
expect_linked(graph, "quix", "baz")
|
||||
)
|
||||
|
||||
func expect_linked(graph: _Graph, from: Variant, to: Variant):
|
||||
expect(graph.has_link(from, to), "Link %s -> %s was not present!")
|
||||
|
||||
func expect_unlinked(graph: _Graph, from: Variant, to: Variant):
|
||||
expect_not(graph.has_link(from, to), "Link %s -> %s was present!" % [from, to])
|
||||
@@ -0,0 +1 @@
|
||||
uid://dm1d6ldar45q8
|
||||
@@ -0,0 +1,69 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "HistoryBuffer"
|
||||
|
||||
func suite() -> void:
|
||||
var empty_buffer := _HistoryBuffer.new()
|
||||
var filled_buffer := _HistoryBuffer.of(16, { 2: "foo", 4: "bar", 8: "baz" })
|
||||
|
||||
define("get latest", func():
|
||||
test("should not have latest if empty", func():
|
||||
expect_not(empty_buffer.has_at(16))
|
||||
expect_not(empty_buffer.has_latest_at(16))
|
||||
)
|
||||
|
||||
test("should not have latest out of bounds", func():
|
||||
expect_not(filled_buffer.has_latest_at(0))
|
||||
)
|
||||
|
||||
test("should return self on known item", func():
|
||||
expect_equal(filled_buffer.get_latest_index_at(2), 2)
|
||||
expect_equal(filled_buffer.get_latest_index_at(4), 4)
|
||||
expect_equal(filled_buffer.get_latest_index_at(8), 8)
|
||||
)
|
||||
|
||||
test("should return latest on unknown", func():
|
||||
expect_equal(filled_buffer.get_latest_index_at(3), 2)
|
||||
expect_equal(filled_buffer.get_latest_index_at(5), 4)
|
||||
expect_equal(filled_buffer.get_latest_index_at(9), 8)
|
||||
)
|
||||
)
|
||||
|
||||
define("set_at()", func():
|
||||
test("should set behind tail", func():
|
||||
var buffer := filled_buffer.duplicate()
|
||||
buffer.set_at(1, 4)
|
||||
expect(buffer.has_at(1))
|
||||
)
|
||||
|
||||
test("should not set behind limit", func():
|
||||
var buffer := filled_buffer.duplicate()
|
||||
buffer.set_at(-64, 4)
|
||||
expect_not(buffer.has_at(-64))
|
||||
)
|
||||
|
||||
test("should update prev buffer if in bounds", func():
|
||||
var buffer := filled_buffer.duplicate()
|
||||
buffer.set_at(6, "quoo")
|
||||
expect_not(buffer.has_at(7))
|
||||
expect(buffer.has_latest_at(7))
|
||||
expect_equal(buffer.get_latest_at(7), "quoo")
|
||||
)
|
||||
|
||||
test("should update prev buffer if after head", func():
|
||||
var buffer := filled_buffer.duplicate()
|
||||
buffer.set_at(14, "quoo")
|
||||
expect_not(buffer.has_at(11))
|
||||
expect(buffer.has_latest_at(11))
|
||||
expect_equal(buffer.get_latest_at(11), "baz")
|
||||
)
|
||||
|
||||
test("should jump if way after head", func():
|
||||
var buffer := filled_buffer.duplicate()
|
||||
buffer.set_at(130, "quoo")
|
||||
expect_not(buffer.has_at(8))
|
||||
expect_not(buffer.has_latest_at(8))
|
||||
expect_equal(buffer.size(), 1)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0u5k1vxoddjo
|
||||
@@ -0,0 +1,30 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "IntervalScheduler"
|
||||
|
||||
func suite() -> void:
|
||||
test("should never schedule on zero interval", func():
|
||||
var interval := _IntervalScheduler.new(0)
|
||||
expect_false(interval.is_now())
|
||||
expect_false(interval.is_now())
|
||||
expect_false(interval.is_now())
|
||||
)
|
||||
|
||||
test("should always schedule on one interval", func():
|
||||
var interval := _IntervalScheduler.new(1)
|
||||
expect_true(interval.is_now())
|
||||
expect_true(interval.is_now())
|
||||
expect_true(interval.is_now())
|
||||
)
|
||||
|
||||
test("should schedule on interval", func():
|
||||
var interval := _IntervalScheduler.new(3)
|
||||
expect_false(interval.is_now())
|
||||
expect_false(interval.is_now())
|
||||
expect_true(interval.is_now())
|
||||
|
||||
expect_false(interval.is_now())
|
||||
expect_false(interval.is_now())
|
||||
expect_true(interval.is_now())
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://014vcn6wjlpc
|
||||
@@ -0,0 +1,122 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "Set"
|
||||
|
||||
#region add() + values()
|
||||
func test_add_should_persist() -> void:
|
||||
# Given
|
||||
var set := _Set.new()
|
||||
var expected := [2, "foo"]
|
||||
|
||||
# When
|
||||
set.add(2)
|
||||
set.add("foo")
|
||||
|
||||
# Then
|
||||
expect_equal(set.values(), expected)
|
||||
expect_equal(set.size(), 2)
|
||||
expect(not set.is_empty())
|
||||
|
||||
#endregion
|
||||
|
||||
#region has()
|
||||
func test_should_have_known_items() -> void:
|
||||
# Given
|
||||
var set := _Set.new()
|
||||
set.add(2)
|
||||
|
||||
# When + Then
|
||||
expect(set.has(2))
|
||||
|
||||
func test_should_not_have_unknown_items() -> void:
|
||||
# Given
|
||||
var set := _Set.new()
|
||||
set.add(2)
|
||||
|
||||
# When + Then
|
||||
expect_not(set.has("Foo"))
|
||||
|
||||
#endregion
|
||||
|
||||
#region size() + is_empty()
|
||||
func test_new_set_should_be_empty() -> void:
|
||||
# Given
|
||||
var set := _Set.new()
|
||||
|
||||
# Then
|
||||
expect(set.is_empty())
|
||||
expect_equal(set.size(), 0)
|
||||
|
||||
func test_set_should_not_be_empty() -> void:
|
||||
# Given
|
||||
var set := _Set.new()
|
||||
|
||||
# When
|
||||
set.add("foo")
|
||||
set.add("bar")
|
||||
|
||||
# Then
|
||||
expect_not(set.is_empty())
|
||||
expect_equal(set.size(), 2)
|
||||
#endregion
|
||||
|
||||
#region erase() + clear()
|
||||
func test_erase_should_remove() -> void:
|
||||
# Given
|
||||
var set := _Set.of(["foo", "bar", "quix"])
|
||||
var expected = ["foo", "quix"]
|
||||
|
||||
# When
|
||||
set.erase("bar")
|
||||
|
||||
# Then
|
||||
expect_equal(set.values(), expected)
|
||||
|
||||
func test_clear_should_make_empty() -> void:
|
||||
# Given
|
||||
var set := _Set.of(["foo", "bar", "quix"])
|
||||
|
||||
# When
|
||||
set.clear()
|
||||
|
||||
# Then
|
||||
expect_empty(set)
|
||||
#endregion
|
||||
|
||||
#region iteration
|
||||
func test_empty_should_not_be_iterable() -> void:
|
||||
# Given
|
||||
var set := _Set.new()
|
||||
|
||||
# When + then
|
||||
expect_not(set._can_iterate(), "Set shouldn't be iterable")
|
||||
|
||||
func test_should_be_iterable() -> void:
|
||||
# Given
|
||||
var set := _Set.of([2, "Foo"])
|
||||
|
||||
# When + then
|
||||
expect(set._can_iterate(), "Set should be iterable")
|
||||
|
||||
func test_iterate_should_yield_values() -> void:
|
||||
# Given
|
||||
var set := _Set.of([1, 2, "Foo", {}])
|
||||
var expected := [1, 2, "Foo", {}]
|
||||
var iterated := []
|
||||
|
||||
# When
|
||||
for item in set:
|
||||
iterated.append(item)
|
||||
|
||||
# Then
|
||||
expect_equal(iterated, expected)
|
||||
#endregion
|
||||
|
||||
func test_min_max() -> void:
|
||||
# Given
|
||||
var set := _Set.of([2, 1, 3])
|
||||
|
||||
# When + Then
|
||||
expect_equal(set.min(), 1)
|
||||
expect_equal(set.max(), 3)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bowionq1hw1gd
|
||||
@@ -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
|
||||
@@ -0,0 +1,45 @@
|
||||
extends Object
|
||||
class_name NetworkMocks
|
||||
|
||||
static var NOOP = func(): pass
|
||||
|
||||
static func set_network_tick(p_tick: int) -> void:
|
||||
NetworkTime._tick = p_tick
|
||||
|
||||
static func set_rollback_tick(p_tick: int) -> void:
|
||||
NetworkRollback._tick = p_tick
|
||||
|
||||
static func set_tick(network_tick: int, rollback_tick: int) -> void:
|
||||
set_network_tick(network_tick)
|
||||
set_rollback_tick(rollback_tick)
|
||||
|
||||
static func set_tickrate(tickrate: int) -> void:
|
||||
NetworkTime._tickrate = tickrate
|
||||
|
||||
static func in_rollback(callback: Callable) -> void:
|
||||
NetworkRollback._is_rollback = true
|
||||
callback.call()
|
||||
NetworkRollback._is_rollback = false
|
||||
|
||||
## Runs [param]callback[/param] in the network tick loop
|
||||
static func in_network_tick_loop(callback: Callable) -> void:
|
||||
NetworkTime.before_tick_loop.emit()
|
||||
callback.call()
|
||||
NetworkTime.after_tick_loop.emit()
|
||||
|
||||
## Runs [param]callback[/param] as part of a network tick
|
||||
static func in_network_tick(callback: Callable) -> void:
|
||||
NetworkTime.before_tick.emit(NetworkTime.ticktime, NetworkTime.tick)
|
||||
NetworkTime.on_tick.emit(NetworkTime.ticktime, NetworkTime.tick)
|
||||
callback.call()
|
||||
NetworkTime.after_tick.emit(NetworkTime.ticktime, NetworkTime.tick)
|
||||
|
||||
NetworkTime._tick += 1
|
||||
|
||||
## Runs a single network tick
|
||||
static func run_network_tick() -> void:
|
||||
in_network_tick(NOOP)
|
||||
|
||||
## Runs a single-tick network tick loop
|
||||
static func run_network_tick_loop() -> void:
|
||||
in_network_tick_loop(NOOP)
|
||||
@@ -0,0 +1 @@
|
||||
uid://blatia26x0byg
|
||||
@@ -0,0 +1,52 @@
|
||||
extends Object
|
||||
class_name SnapshotFixtures
|
||||
|
||||
static func state_snapshot(p_position: Vector3 = Vector3.ONE) -> _PropertySnapshot:
|
||||
return _PropertySnapshot.from_dictionary({
|
||||
":position": p_position,
|
||||
":velocity": Vector3.ZERO,
|
||||
":health": 100.
|
||||
})
|
||||
|
||||
static func state_properties() -> Array[String]:
|
||||
var result: Array[String] = [
|
||||
":position",
|
||||
":velocity",
|
||||
":health"
|
||||
]
|
||||
return result
|
||||
|
||||
static func state_propery_entries(root_node: Node) -> Array[PropertyEntry]:
|
||||
var result: Array[PropertyEntry] = []
|
||||
result.assign(state_properties().map(func(path):
|
||||
return PropertyEntry.parse(root_node, path)
|
||||
))
|
||||
return result
|
||||
|
||||
static func state_node() -> StateNode:
|
||||
return StateNode.new()
|
||||
|
||||
static func input_snapshot(p_movement: Vector3 = Vector3.ONE) -> _PropertySnapshot:
|
||||
return _PropertySnapshot.from_dictionary({
|
||||
"Input:movement": p_movement,
|
||||
"Input:is_jumping": false
|
||||
})
|
||||
|
||||
static func input_property_entries(root_node: Node) -> Array[PropertyEntry]:
|
||||
var result: Array[PropertyEntry] = []
|
||||
result.append(PropertyEntry.parse(root_node, "Input:movement"))
|
||||
result.append(PropertyEntry.parse(root_node, "Input:is_jumping"))
|
||||
|
||||
return result
|
||||
|
||||
static func input_node() -> InputNode:
|
||||
var result := InputNode.new()
|
||||
result.name = "Input"
|
||||
return result
|
||||
|
||||
class InputNode extends Node:
|
||||
var movement := Vector3.ZERO
|
||||
var is_jumping := false
|
||||
|
||||
class StateNode extends Node3D:
|
||||
var health := 100.
|
||||
@@ -0,0 +1 @@
|
||||
uid://bp8nmv0qlivqq
|
||||
Reference in New Issue
Block a user