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:
2026-07-02 20:55:20 -04:00
parent ce39b237c3
commit b0c83af092
4416 changed files with 57418 additions and 902676 deletions
@@ -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
+59
View File
@@ -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)
)
+1
View File
@@ -0,0 +1 @@
uid://cmvjg7a72hjmg
+40
View File
@@ -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)
)
+1
View File
@@ -0,0 +1 @@
uid://bil8ugs8ol5h4
+36
View File
@@ -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()
)
+1
View File
@@ -0,0 +1 @@
uid://brow8r8ya3xa0
+48
View File
@@ -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])
+1
View File
@@ -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
+122
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
uid://bowionq1hw1gd
+112
View File
@@ -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
+34
View File
@@ -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
+22
View File
@@ -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
+138
View File
@@ -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
+77
View File
@@ -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
+147
View File
@@ -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
+52
View File
@@ -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
+45
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
uid://blatia26x0byg
+52
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
uid://bp8nmv0qlivqq