OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps_demo / hypergodot / hyper_gossip / hyper_gossip.gd

172 lines · gd ref:
extends Node

class_name HyperGossip

const LRU = preload("../utils/lru.gd")

signal listening(extension_name)
signal event(type, data, from)
signal peers(peers)

export var url = "hyper://blog.mauve.moe/"
export var extension_name = "hyper-gossip-v1"
export var pool_size = 8
export var queue_warning_size = 8

var hyperGateway : HyperGateway

var peerListRequest = HyperRequest.new()
var eventSource = HyperEventSource.new()

# Pool of HyperRequest objects to send multiple broadcasts at once
var requestPool = []
# Queue of requests that should be sent out
var requestQueue = []
# Track whether we have warned about sending more requests than can be handled
var hasWarnedCapacity = false
var seen_messages = LRU.new()

func _ready():
	randomize()
	
	add_child(peerListRequest)
	peerListRequest.connect("request_completed", self, "_on_peer_list")
	peerListRequest.use_threads = true

	for _i in range(pool_size):
		var broadcastRequest = HyperRequest.new()
		broadcastRequest.use_threads = true
		add_child(broadcastRequest)
		broadcastRequest.connect("request_completed", self, "_on_broadcast_completed")
		requestPool.push_back(broadcastRequest)

	add_child(eventSource)
	eventSource.connect("event", self, "_on_event")
	eventSource.connect("response_started", self, "_on_events_started")

func start_listening():
	load_peers()
	eventSource.request(_get_extension_listen_url())

func _on_events_started(_statusCode, _responseHeaders):
	emit_signal("listening", extension_name)

func _enqueue_broadcast_request(reqURL, body):
	var broadcastRequest = _get_next_request()
	if broadcastRequest == null:
		print('Queueing')
		requestQueue.push_back({"reqURL": reqURL, "body": body})
		if requestQueue.size() > queue_warning_size && !hasWarnedCapacity:
			hasWarnedCapacity = true
			print_debug("Warning: You have more gossip events than your queue_warning_size permits, either slow down the rate or increase the pool_size to send more");
		return

	broadcastRequest.request(reqURL, [], false, HTTPClient.METHOD_POST, body)	

func _get_next_request():
	for request in requestPool:
		var status = request.get_http_client_status()
		if status == HTTPClient.STATUS_DISCONNECTED:
			return request
	return null

func _on_broadcast_completed(_result, _response_code, _headers, _body):
	if requestQueue.size() == 0: return
	var next = requestQueue.pop_front()
	_enqueue_broadcast_request(next.reqURL, next.body)

func rebroadcast(event):
	var reqURL = _get_extension_url()
	var body = JSON.print(event)
	_enqueue_broadcast_request(reqURL, body)

func broadcast_event(type: String, data):
	if(hyperGateway == null):
		# TODO : Make the link to HyperGateway static
		hyperGateway = get_tree().get_current_scene().get_node("HyperGodot").get_node("HyperGateway")
	if(hyperGateway != null):
		if( hyperGateway.getIsGatewayRunning() ):
			var reqURL = _get_extension_url()
			var body = JSON.print(_generateEvent(type, data))
			_enqueue_broadcast_request(reqURL, body)
	
func send_to_peer(peer, type, data):
	var reqURL = _get_extension_url_to_peer(peer)
	var body = JSON.print(_generateEvent(type, data))
	_enqueue_broadcast_request(reqURL, body)

func load_peers():
	var reqURL = _get_extension_url()
	peerListRequest.request(reqURL)

func _generateEvent(type, data):
	var id = _make_id()
	seen_messages.track(id)
	return {
		"id": id,
		"type": type,
		"data": data
	}

func _handle_event(event):
	var type = event.type
	var data = event.data
	var id = event.id
	var from = event.from
	
	if seen_messages.has(id):
		return
	
	seen_messages.track(id)
	
	emit_signal("event", type, data, from)
	rebroadcast(event)

func _on_event(data, event, id):
	# Ignore events for other extensions
	if event != extension_name: return

	var parsed = JSON.parse(data)
	
	if parsed.error != OK:
		printerr("Unable to parse EventSource content " + parsed.error_line + "\n" + data)
		return
	
	var result = parsed.result
	if !("from" in result):
		result.from = id

	_handle_event(result)
	

func _on_peer_list(_result, response_code, _headers, body):
	var text = body.get_string_from_utf8()
	if response_code != 200:
		printerr('Error listing peers ', response_code, " ", text)
		return

	var parsed = JSON.parse(text)

	if parsed.error != OK:
		printerr("Unable to parse Peer List" + String(parsed.error_line))
		return
		
	var peers = parsed.result

	emit_signal("peers", peers)

func _get_extension_url_to_peer(peer):
	return url + '$/extensions/' + extension_name + '/' + peer

func _get_extension_url():
	return url + '$/extensions/' + extension_name

func _get_extension_listen_url():
	return url + '$/extensions/'
	
func _get_url() -> String:
	return url

func _make_id():
	return str(randi()) + str(randi())