OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / integration / hello-world / hello.gd

122 lines · gd ref:
extends SceneTree
## Hello World — GDScript side of the Hyperswarm interop test.
##
## Joins the live public DHT on a shared topic, discovers peers,
## and exchanges messages via protomux "godot-rpc" channel (type 0).
##
## Usage:
##   godot4 --headless --path project --script res://hello.gd -- --topic hello-hyperswarm
##
## The --topic arg defaults to "hello-hyperswarm" if not provided.

const POLL_HZ := 0.05
const SEND_INTERVAL := 5.0

var _peer: Object
var _dht_kp: Dictionary
var _my_pk_hex := ""
var _topic_str := "hello-hyperswarm"
var _seq := 0
var _connected_peers: Dictionary = {}


func _init() -> void:
	var args := OS.get_cmdline_user_args()
	for i in args.size():
		if args[i] == "--topic" and i + 1 < args.size():
			_topic_str = args[i + 1]
	_start.call_deferred()


func _start() -> void:
	print("[hello-godot] topic: \"%s\"" % _topic_str)
	print("[hello-godot] pid: %d" % OS.get_process_id())

	# Create peer
	_peer = ClassDB.instantiate(&"HyperswarmMultiplayerPeer")

	# Generate DHT keypair
	_dht_kp = _peer.generate_dht_keypair()
	var pk: PackedByteArray = _dht_kp["public_key"]
	_my_pk_hex = pk.hex_encode()
	print("[hello-godot] pk: %s" % _my_pk_hex.left(12))

	# Connect signals
	_peer.peer_connected.connect(_on_peer_connected)
	_peer.peer_disconnected.connect(_on_peer_disconnected)

	# Join DHT — empty bootstrap = use hardcoded live DHT defaults
	var empty_bootstrap := PackedStringArray()
	var err: int = _peer.join_dht(pk, _dht_kp["secret_key"], empty_bootstrap)
	if err != OK:
		print("[hello-godot] FAIL: join_dht error %d" % err)
		quit()
		return

	# Wait for DHT to become active
	print("[hello-godot] joining live DHT...")
	var elapsed := 0.0
	while elapsed < 30.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):
		print("[hello-godot] FAIL: DHT not active after 30s")
		_peer.close()
		quit()
		return
	print("[hello-godot] DHT active")

	# Join swarm topic
	var topic_hash: PackedByteArray = _topic_str.sha256_buffer()
	print("[hello-godot] topic hash: %s..." % topic_hash.hex_encode().left(16))
	err = _peer.swarm_join(topic_hash, true, true)
	if err != OK:
		print("[hello-godot] FAIL: swarm_join error %d" % err)
		_peer.close()
		quit()
		return
	print("[hello-godot] topic joined, waiting for peers...\n")

	# Main poll loop — runs until Ctrl+C
	var send_timer := 0.0
	while true:
		_peer.poll()

		# Receive packets
		while _peer.get_available_packet_count() > 0:
			var pkt: PackedByteArray = _peer.get_packet()
			var text: String = pkt.get_string_from_utf8()
			print("[recv] %s" % text)

		# Send every SEND_INTERVAL seconds
		send_timer += POLL_HZ
		if send_timer >= SEND_INTERVAL:
			send_timer = 0.0
			if _connected_peers.size() > 0:
				_seq += 1
				var payload: String = JSON.stringify({
					"from": "godot",
					"msg": "Hello World",
					"seq": _seq,
				})
				_peer.set_target_peer(0)
				_peer.set_transfer_mode(MultiplayerPeer.TRANSFER_MODE_RELIABLE)
				_peer.put_packet(payload.to_utf8_buffer())
				print("[send] %s" % payload)

		await create_timer(POLL_HZ).timeout


func _on_peer_connected(id: int) -> void:
	_connected_peers[id] = true
	print("[connected] peer_id=%d (total: %d)" % [id, _connected_peers.size()])


func _on_peer_disconnected(id: int) -> void:
	_connected_peers.erase(id)
	print("[disconnected] peer_id=%d (total: %d)" % [id, _connected_peers.size()])