OverviewFilesCommitsIssuesDocsReleasesPeers

hypergodot commits / 7b047d5

Progress on hyperchat extension message demo
7b047d5a9bee5dcc90441cbe1e69acadaa5a65c9
by mauve <ranger@mauve.moe> · 2021-08-25T17:52:27.000Z

Changed files (8) +272 -3

README.md +7 -2
……@@ -17,10 +17,15 @@ A Godot plugin and example project for loading assets from the [hypercore-protoc
1717- [x] Example for saving data to hyper
1818- [x] Make FTP server that can be mounted as a network drive
1919- [x] Listen for changes (EventSource)
20- [ ] Listen for download progress (EventSource)
21- [ ] Listen for / Publish extension messages (EventSource)
20- [x] Listen for download progress (EventSource)
21- [x] Listen for / Publish extension messages (EventSource)
2222- [ ] Examples for all the methods exposed by the gateway.
23 - [x] GET/PUT text files
24 - [x] Watch for changes
25 - [ ] Basic chat app (WIP
2326- [ ] Basic Multiplayer demo using extension messages
27- [ ] Load gltf scene from hyper
28- [ ] Load VRM avatar from hyper (social multiplayer) [VRM stuff](https://github.com/V-Sekai/godot-vrm/blob/godot3/addons/vrm/import_vrm.gd)
2429- [ ] Folder sync (From `hyper://` to fs, from fs to `hyper://`)
2530
2631## Supported APIs:
hyper-gateway/hyper-gateway-linux +0 -0
(no content changes — binary or rename)
hyper-gateway/hyper-gateway-macos +0 -0
(no content changes — binary or rename)
hyper-gateway/hyper-gateway-windows.exe +0 -0
(no content changes — binary or rename)
src/HyperChat/HyperChat.gd +155 -0
……@@ -0,0 +1,155 @@
1extends "res://src/ResizeRoot.gd"
2
3const LRU = preload('res://src/utils/LRU.gd')
4
5# Messages are JSON which contain {id, from, username, type, content}
6# All messages have a username and type
7# Some messages contain a `from` for peer that sent the message
8# If no `from` field is visible, it means it's from the sending peer
9# type: 'text` will have `content` of the text message that got sent
10# type: 'identity' will have empty `content`, and is there to notify peers of this users existence
11
12# GET peer list to start registering for extensions
13# Set up list of seen remotePublicKeys
14# Broadcast self after a second of waiting for peers
15# Broadcast self on name change
16# Broadcast self when seeing a new remotePublicKey
17
18export var archiveRoot = 'hyper://blog.mauve.moe/'
19export var extensionName = 'hyperchat-example-1'
20
21var extensionsFolder = archiveRoot + '$/extensions/'
22var extensionPath = extensionsFolder + extensionName
23
24var seen_messages = LRU.new()
25var seen_identities = LRU.new()
26
27onready var eventSource = $HyperGateway/HyperEventSource
28onready var gateway = $HyperGateway
29onready var chatbox = $ScrollContainer/CatBox
30onready var chatText = $HBoxContainer/ChatInput
31onready var username = $HBoxContainer/Username
32onready var broadcastRequest = $HyperGateway/BroadcastRequest
33onready var listPeersRequest = $HyperGateway/ListPeersRequest
34
35func _ready():
36 # Initialize random number generator
37 randomize()
38 gateway.ensureSetup()
39
40func display_item(from, content):
41 # Add to item list
42 chatText.text += from + ': ' + content + "\n"
43
44func send_current():
45 var text = chatText.text
46 chatText.text = ""
47 broadcast_text(text)
48
49func update_identity(from, username):
50 # Have some sort of sidebar to display known users?
51 display_item('System', 'Seen user ' + username)
52 pass
53
54func list_peers():
55 listPeersRequest.request(extensionPath)
56
57func _on_text_message(data, from):
58 # TODO: Something fancy with the `from` attribute?
59 display_item(data.username, data.content)
60
61func _on_identity_message(data, from):
62 var isNew = !has_seen_identity(from)
63
64 # Track identity
65 seen_identities.track(from)
66
67 # If it's new, broadcast your own identity
68 if isNew:
69 broadcast_identity()
70
71func _on_unknown_message(data, from):
72 # Emit some sort of error?
73 printerr("Got unknown message", from, data)
74
75func get_username():
76 return username.text
77
78func broadcast_text(content):
79 var username = get_username()
80 broadcast_message({
81 'type': 'text',
82 'content': content,
83 'username': username
84 })
85
86func broadcast_identity():
87 var username = get_username()
88 broadcast_message({
89 'type': 'identity',
90 'username': username
91 })
92
93func broadcast_message(message):
94 # POST to extension URL
95 if !message.has('id'):
96 message.id = make_id()
97 seen_messages.track(message.id)
98 print('Broadcasting message', message)
99 var body = JSON.print(message)
100 broadcastRequest.request(extensionPath,[], false, HTTPClient.METHOD_GET, body)
101
102func has_seen_identity(from):
103 return seen_identities.has(from)
104
105func has_seen_message(id):
106 return seen_messages.has(id)
107
108func re_broadcast(message, from):
109 if !message.has('from'):
110 message.from = from
111 broadcast_message(message)
112
113func handle_message(message, from):
114 var finalFrom = message.from if message.has('from') else from
115 if !message.has('id'):
116 return _on_unknown_message(message, finalFrom)
117 if !has_seen_message(message.id):
118 re_broadcast(message, finalFrom)
119 match message.type:
120 'text':
121 _on_text_message(message, finalFrom)
122 'identity':
123 _on_identity_message(message, finalFrom)
124 _:
125 _on_unknown_message(message, finalFrom)
126
127func _on_event(data, event, id):
128 var parsed = JSON.parse(data)
129
130 if parsed.error != OK:
131 printerr("Unable to parse EventSource content " + parsed.error_line + "\n" + data)
132 return
133
134 handle_message(parsed.result, id)
135
136func make_id():
137 return str(randi()) + str(randi())
138
139func start_listening():
140 eventSource.request(extensionsFolder)
141
142func _on_HyperEventSource_event(data, event, id):
143 _on_event(data, event, id)
144
145func _on_HyperGateway_started_gateway(pid):
146 start_listening()
147 broadcast_identity()
148
149func _on_SendButton_pressed():
150 send_current()
151 pass # Replace with function body.
152
153func _on_ChatInput_text_entered(_new_text):
154 send_current()
155 pass # Replace with function body.
src/HyperChat/HyperChat.tscn +67 -0
……@@ -0,0 +1,67 @@
1[gd_scene load_steps=5 format=2]
2
3[ext_resource path="res://src/HyperChat/HyperChat.gd" type="Script" id=1]
4[ext_resource path="res://src/HyperGateway/HyperGateway.tscn" type="PackedScene" id=2]
5[ext_resource path="res://src/HyperRequest/HyperEventSource.gd" type="Script" id=3]
6[ext_resource path="res://src/HyperRequest/HyperRequest.gd" type="Script" id=4]
7
8[node name="Control" type="VBoxContainer"]
9anchor_right = 1.0
10anchor_bottom = 1.0
11script = ExtResource( 1 )
12__meta__ = {
13"_edit_use_anchors_": false
14}
15
16[node name="HBoxContainer" type="HBoxContainer" parent="."]
17margin_right = 666.0
18margin_bottom = 24.0
19
20[node name="ChatInput" type="LineEdit" parent="HBoxContainer"]
21margin_right = 487.0
22margin_bottom = 24.0
23size_flags_horizontal = 3
24
25[node name="SendButton" type="Button" parent="HBoxContainer"]
26margin_left = 491.0
27margin_right = 534.0
28margin_bottom = 24.0
29text = "Send"
30
31[node name="Username" type="LineEdit" parent="HBoxContainer"]
32margin_left = 538.0
33margin_right = 666.0
34margin_bottom = 24.0
35rect_min_size = Vector2( 128, 0 )
36text = "Anonymous"
37
38[node name="ScrollContainer" type="ScrollContainer" parent="."]
39margin_top = 28.0
40margin_right = 666.0
41margin_bottom = 666.0
42size_flags_horizontal = 3
43size_flags_vertical = 3
44
45[node name="ChatBox" type="RichTextLabel" parent="ScrollContainer"]
46margin_right = 666.0
47margin_bottom = 638.0
48size_flags_horizontal = 3
49size_flags_vertical = 3
50text = "System: Enter some text and hit `send` to broadcast it to peers.
51"
52
53[node name="HyperGateway" parent="." instance=ExtResource( 2 )]
54
55[node name="HyperEventSource" type="Node" parent="HyperGateway"]
56script = ExtResource( 3 )
57
58[node name="BroadcastRequest" type="HTTPRequest" parent="HyperGateway"]
59script = ExtResource( 4 )
60
61[node name="ListPeersRequest" type="HTTPRequest" parent="HyperGateway"]
62script = ExtResource( 4 )
63
64[connection signal="text_entered" from="HBoxContainer/ChatInput" to="." method="_on_ChatInput_text_entered"]
65[connection signal="pressed" from="HBoxContainer/SendButton" to="." method="_on_SendButton_pressed"]
66[connection signal="started_gateway" from="HyperGateway" to="." method="_on_HyperGateway_started_gateway"]
67[connection signal="event" from="HyperGateway/HyperEventSource" to="." method="_on_HyperEventSource_event"]
src/HyperLoad/HyperLoad.tscn +1 -1
……@@ -88,5 +88,5 @@ autoStart = true
8888[connection signal="started_gateway" from="HyperGateway" to="StatusLabel" method="setGatewayLoaded"]
8989[connection signal="starting_gateway" from="HyperGateway" to="StatusLabel" method="setGatewayLoading"]
9090[connection signal="loading_data" from="HyperGateway/HyperRequest" to="StatusLabel" method="setLoading"]
91[connection signal="request_completed" from="HyperGateway/HyperRequest" to="StatusLabel" method="_on_HyperLoad_request_completed"]
9291[connection signal="request_completed" from="HyperGateway/HyperRequest" to="ScrollContainer/Output" method="_on_HyperLoad_request_completed"]
92[connection signal="request_completed" from="HyperGateway/HyperRequest" to="StatusLabel" method="_on_HyperLoad_request_completed"]
src/utils/LRU.gd +42 -0
……@@ -0,0 +1,42 @@
1# Least Recently Used Cache
2## Use this if you want to keep track of a set, but with a limit
3
4# Map from value to time last used
5var values = {}
6const DEFAULT_SIZE = 512
7
8var max_size = DEFAULT_SIZE
9
10func _init(new_max = DEFAULT_SIZE):
11 max_size = new_max
12
13func has(value):
14 return value in values
15 pass
16
17func track(value):
18 var time = OS.get_system_time_msecs()
19
20 values[value] = time
21 pass
22
23func is_full():
24 return size() > max_size
25
26func size():
27 return values.size()
28
29func clear_oldest():
30 var oldest = get_oldest()
31 values.erase(oldest)
32 pass
33
34func get_oldest():
35 var oldestTime = OS.get_system_time_msecs()
36 var oldestValue = null
37 for value in values.keys():
38 var valueTime = values[value]
39 if valueTime < oldestTime:
40 oldestTime = valueTime
41 oldestValue = value
42 return oldestValue