Brücke für Host und Gast

GwSession führt eine Sitzung als Host oder als Gast und reicht die
Nachrichten des Kerns an GDScript weiter. Nur der Host speichert. Die
Behandlung interner Fehler liegt jetzt in einem reinen Rust-Teil und hat
einen Test, und das Ereignis join_refused nennt den Schlüssel.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Steven Wroblewski
2026-10-01 19:20:34 +02:00
parent 155d90f3a5
commit 2faa3ccce3
5 changed files with 671 additions and 128 deletions
+96
View File
@@ -138,3 +138,99 @@ func test_the_state_hash_changes_with_every_tick() -> void:
session.step()
check_eq(first.length(), 16, "sixteen hexadecimal digits")
check(session.state_hash_hex() != first, "the tick is part of the state")
var guest_key := PackedByteArray([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
## Carries every waiting message between `host` and `guest`, which is
## connected to the host as peer 5, until none are left. Returns the notices
## the guest got.
func _carry(host: GwSession, guest: GwSession) -> Array[Dictionary]:
var notices: Array[Dictionary] = []
var moved := true
while moved:
moved = false
for message in guest.take_messages():
check_eq(message["peer"], GwSession.HOST_PEER, "a guest writes to its host")
host.receive(5, message["bytes"])
moved = true
for message in host.take_messages():
if message["peer"] == 5:
notices.append_array(guest.receive(GwSession.HOST_PEER, message["bytes"]))
moved = true
return notices
## A host whose player Ada is in the world, and the guest Grace at peer 5,
## whose join has run.
func _host_and_guest() -> Array[GwSession]:
var host := _joined_session()
host.connect_peer(5)
var guest := GwSession.new()
guest.start_guest(guest_key, "Grace")
_carry(host, guest)
host.step()
_carry(host, guest)
while guest.pending_ticks() > 0:
guest.step()
return [host, guest]
func test_a_guest_follows_its_host_through_the_bridge() -> void:
var host := _joined_session()
host.connect_peer(5)
var guest := GwSession.new()
check(guest.start_guest(guest_key, "Grace"), "the guest starts")
check(not guest.is_host(), "as a guest")
check(not guest.is_running(), "without a world before the host answers")
check_eq(_carry(host, guest), [{"type": "world_received", "resync": false}], "the host's world arrives")
check_eq(guest.tick(), host.tick(), "at the host's tick")
for tick in 25:
host.step()
_carry(host, guest)
check_eq(guest.pending_ticks(), 25, "the ticks wait until the guest runs them")
while guest.pending_ticks() > 0:
guest.step()
check_eq(guest.state_hash_hex(), host.state_hash_hex(), "both worlds are the same")
check_eq(guest.local_player(), 1, "the guest is player 1")
check_eq(host.player_of_peer(5), 1, "the host knows whose connection it is")
check_eq(host.guest_count(), 1, "one guest")
check_eq(guest.player_name(0), "Ada", "the guest knows the host's player")
check_eq(guest.online_players(), PackedInt64Array([0, 1]), "two players online")
check_eq(guest.save().size(), 0, "a guest never saves the host's world")
check(guest.recording_bytes().size() > 0, "but it records its session")
func test_the_hit_of_a_guest_becomes_a_message_to_the_host() -> void:
var pair := _host_and_guest()
var host := pair[0]
var guest := pair[1]
var rock := _first_soft_rock(host)
guest.dig(rock.x, rock.y)
_carry(host, guest)
host.step()
_carry(host, guest)
var events: Array[Dictionary] = []
while guest.pending_ticks() > 0:
events.append_array(guest.step())
check_eq(events.map(func(event: Dictionary) -> String: return event["type"]), ["tile_hit"], "the guest sees its hit")
check_eq(host.damage_codes()[rock.y * host.map_width() + rock.x], 1, "and so does the host")
func test_a_guest_learns_why_it_is_turned_away() -> void:
var host := _joined_session()
host.connect_peer(5)
var twin := GwSession.new()
twin.start_guest(key, "Ada")
check_eq(_carry(host, twin), [{"type": "refused", "reason": GwSession.REFUSED_ALREADY_PLAYING}], "Ada is in the world already")
check_eq(host.take_closing(), PackedInt64Array([5]), "the host gives up the connection")
func test_an_unreadable_message_from_the_host_is_reported() -> void:
var guest := GwSession.new()
guest.start_guest(guest_key, "Grace")
check_eq(guest.receive(GwSession.HOST_PEER, PackedByteArray([0xEE])), [{"type": "protocol_error"}], "a notice")
check(guest.last_error().contains("unreadable"), "with the reason: %s" % guest.last_error())
check(not guest.start_guest(PackedByteArray([1, 2]), "Grace"), "a short key is refused")