clockwork-city/addons/tube/tube_context.gd

153 lines
4.5 KiB
GDScript3
Raw Permalink Normal View History

2026-04-13 11:34:00 -05:00
@icon("./icons/tube_context.svg")
@tool
class_name TubeContext extends Resource
## A resource that holds configuration and helper methods for managing simple multiplayer session.
## Character set to generate app IDs. Contains most printable ASCII characters.
const _APP_ID_CHARACTER_SET := "!#$%&()*+,-./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:;<=>?@[]^_{|}~"
@export_tool_button("Generate app id", "RandomNumberGenerator") var _generate_app_id_tool_button = (func():
app_id = _get_random_string(15, _APP_ID_CHARACTER_SET)
)
## Application identifier for this multiplayer context.
## Must be exactly 15 ASCII characters long.
@export var app_id: String
## Character set used to generate session IDs.
## Must not be empty and should only contain ASCII characters.
## A larger set reduces the probability of collision. With 62 characters
## (AZ, az, 09), the chance of two random 5-character IDs matching is approximately 1 in 916 million.
## For readability by players, consider removing ambiguous characters (e.g., oO0, ilj1I, z2).
@export_multiline var session_id_characters_set: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
## List of tracker server URLs used for session signaling.
@export var trackers_urls: Array[String] = []
## List of STUN server URLs used for WebRTC ICE candidate resolution.
@export var stun_servers_urls: Array[String] = []
## List of TURN servers (optional). Turn server are dictionnary in the form:
## [codeblock]
## {
## "urls": "turn:turn.example.com:3478",
## "username: "my-username",
## "credential": "my-credential",
## }
@export var turn_servers: Array[Dictionary] = []
func _to_string() -> String:
return "AppID: %s | Trackers: %s | STUN: %s" % [app_id, str(trackers_urls), str(stun_servers_urls)]
func _is_ascii(string: String) -> bool:
for char_index in range(string.length()):
if string.unicode_at(char_index) >= 128:
return false
return true
## Checks if the context configuration is valid.
func is_valid() -> bool:
if 0 == session_id_characters_set.length():
printerr("Session ID Character Set is empty")
return false
if not _is_ascii(session_id_characters_set):
printerr("Session ID Character Set can only contain ASCII characters")
return false
if null == app_id or 15 != app_id.length() or not _is_ascii(app_id):
printerr("App id is invalid")
return false
return true
## Returns ICE server configuration dictionary for WebRTC peer connection.
##
## Example:
## [codeblock]
## {
## "iceServers": [
## {
## "urls": [ "stun:stun.example.com:3478" ], # One or more STUN servers.
## },
## {
## "urls": [ "turn:turn.example.com:3478" ], # One or more TURN servers.
## "username": "a_username", # Optional username for the TURN server.
## "credential": "a_password", # Optional password for the TURN server.
## }
## ]
## }
## [/codeblock]
func get_ice_servers() -> Dictionary:
var ice_servers := []
if null != stun_servers_urls:
for url in stun_servers_urls:
ice_servers.append({
"urls": url
})
if null != turn_servers:
for turn_server in turn_servers:
ice_servers.append(turn_server)
if ice_servers.is_empty():
return {}
return {
"iceServers": ice_servers
}
func _get_random_string(p_size: int, character_set: String) -> String:
var rng := RandomNumberGenerator.new()
rng.randomize()
var character_set_length := character_set.length()
var out := ""
for i in range(p_size):
var index := rng.randi()%character_set_length
out += character_set[index]
return out
## Generates a random 5-character session ID.
func generate_session_id() -> String:
return _get_random_string(5, session_id_characters_set)
## Validates if a session ID is correct
func is_session_id_valid(p_session_id: String) -> bool:
return 5 == p_session_id.length()
## Validates if a peer ID hash is numeric and valid.
func is_peer_id_hash_valid(p_peer_id_hash: String) -> bool:
return p_peer_id_hash.is_valid_int()
## Returns the combined "info hash" (app ID and session ID) for tracker usage.
func get_info_hash(p_session_id: String) -> String:
if not is_session_id_valid(p_session_id):
printerr("Invalid session id")
return ""
return app_id + p_session_id
## Converts a integer peer ID hash into an peer ID hash for tracker usage.
func get_peer_id_hash(p_peer_id: int) -> String:
return str(p_peer_id).pad_zeros(20)
## Converts a peer ID hash into an integer peer ID.
func get_peer_id(p_peer_id_hash: String) -> int:
if not is_peer_id_hash_valid(p_peer_id_hash):
return 0
return int(p_peer_id_hash)