extends SceneTree
## Layer 2 — Peer A: DHT Announcer + Listener
##
## True P2P: single peer instance.
## 1. join_dht → bootstrap into local testnet
## 2. listen_on_dht → accept incoming DHT connections
## 3. announce_topic → make discoverable
## 4. Wait for peer_connected (Peer B connects via connect_to_peer)
## 5. Exchange packets
const TIMEOUT := 30.0
const STATUS_FILE := "/tmp/layer2_peer_a_status.txt"
const POLL_HZ := 0.05
var _peer: Object
var _done := false
var _connected_id := -1
func _init() -> void:
_write_status("STARTING")
_start.call_deferred()
func _start() -> void:
# Read bootstrap
var f := FileAccess.open("/tmp/layer_bootstrap.txt", FileAccess.READ)
if f == null:
_fail("Cannot read bootstrap")
return
var bootstrap := PackedStringArray(f.get_as_text().strip_edges().split(","))
f.close()
print("[A] bootstrap: %s" % str(bootstrap))
# Single peer — DHT mode
_peer = ClassDB.instantiate(&"HyperswarmMultiplayerPeer")
var kp: Dictionary = _peer.generate_dht_keypair()
var pk: PackedByteArray = kp["public_key"]
var sk: PackedByteArray = kp["secret_key"]
print("[A] dht pk: %s" % pk.hex_encode().left(16))
# Write our public key so Peer B knows who we are
_write_file("/tmp/layer2_peer_a_dht_pk.txt", pk.hex_encode())
# Connect signals
_peer.peer_connected.connect(_on_peer_connected)
# Join DHT
var err = _peer.join_dht(pk, sk, bootstrap)
if err != OK:
_fail("join_dht: %d" % err)
return
# Wait for DHT to become active
var elapsed := 0.0
while elapsed < 10.0:
_peer.poll()
if _peer.get_dht_status().get("active", false):
break
await create_timer(POLL_HZ).timeout
elapsed += POLL_HZ
if not _peer.get_dht_status().get("active", false):
_fail("DHT not active after 10s")
return
print("[A] DHT active")
# Listen for incoming DHT connections (sets us to server mode, unique_id=1)
err = _peer.listen_on_dht()
if err != OK:
_fail("listen_on_dht: %d" % err)
return
# Announce topic so Peer B can discover us
var topic := "test-p2p-layer2".sha256_buffer()
err = _peer.announce_topic(topic)
if err != OK:
_fail("announce_topic: %d" % err)
return
_write_status("LISTENING")
print("[A] listening on DHT, announced topic, waiting for peer B...")
# Main poll loop
elapsed = 0.0
while elapsed < TIMEOUT and not _done:
_peer.poll()
if _connected_id > 0:
while _peer.get_available_packet_count() > 0:
var pkt: PackedByteArray = _peer.get_packet()
var text := pkt.get_string_from_utf8()
print("[A] received: '%s'" % text)
if text == "hello from B":
_write_status("PASS")
print("[A] SUCCESS — P2P packet exchange complete")
_done = true
break
await create_timer(POLL_HZ).timeout
elapsed += POLL_HZ
if not _done:
_fail("Timeout after %.1fs" % elapsed)
else:
_peer.close()
quit()
func _on_peer_connected(id: int) -> void:
print("[A] peer_connected: id=%d" % id)
_connected_id = id
_write_status("CONNECTED")
# Send hello
var msg := "hello from A".to_utf8_buffer()
_peer.set_target_peer(id)
_peer.set_transfer_mode(MultiplayerPeer.TRANSFER_MODE_RELIABLE)
_peer.put_packet(msg)
print("[A] sent 'hello from A'")
func _write_file(path: String, content: String) -> void:
var wf := FileAccess.open(path, FileAccess.WRITE)
wf.store_string(content)
wf.close()
func _write_status(status: String) -> void:
_write_file(STATUS_FILE, status)
func _fail(msg: String) -> void:
print("[A] FAIL: %s" % msg)
_write_status("FAIL: %s" % msg)
if _peer:
_peer.close()
quit()