summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander M Pickering <alex@cogarr.net>2025-01-12 22:45:37 -0600
committerAlexander M Pickering <alex@cogarr.net>2025-01-12 22:45:37 -0600
commit90ee66a3a6aae10fd84f3f43844db55229933e37 (patch)
treef723f918871c3296636ef2538a1a29a23050e520
parentdecb72f936060a65bff18e9cbf33642ea3a71cd0 (diff)
downloadggj25-90ee66a3a6aae10fd84f3f43844db55229933e37.tar.gz
ggj25-90ee66a3a6aae10fd84f3f43844db55229933e37.tar.bz2
ggj25-90ee66a3a6aae10fd84f3f43844db55229933e37.zip
work
-rw-r--r--spec/net_spec.lua2
-rw-r--r--spec/rng_spec.lua25
-rw-r--r--src/controller.moon16
-rw-r--r--src/controller_bridge.js36
-rw-r--r--src/controller_test.moon8
-rw-r--r--src/js_bridge.js66
-rw-r--r--src/main.lua7
-rw-r--r--src/net.moon114
-rw-r--r--src/net_test.moon44
-rw-r--r--src/preload.lua8
-rw-r--r--src/rng.moon30
-rw-r--r--src/router.moon245
-rw-r--r--src/router_test.moon49
-rw-r--r--src/ui.moon6
-rw-r--r--src/ui/button.moon4
-rw-r--r--src/ui/textbox.moon2
-rw-r--r--src/util.lua9
-rw-r--r--src/world.moon9
18 files changed, 603 insertions, 77 deletions
diff --git a/spec/net_spec.lua b/spec/net_spec.lua
index f3c24eb..82686e6 100644
--- a/spec/net_spec.lua
+++ b/spec/net_spec.lua
@@ -7,7 +7,7 @@ describe("net module", function()
assert(net.Peer)
local peer = assert(net.Peer())
end)
- it("should generate a random id when not supplied #dev", function()
+ it("should generate a random id when not supplied", function()
require("preload")
local net = require("net")
local peer = assert(net.Peer())
diff --git a/spec/rng_spec.lua b/spec/rng_spec.lua
new file mode 100644
index 0000000..731e842
--- /dev/null
+++ b/spec/rng_spec.lua
@@ -0,0 +1,25 @@
+describe("rng module #dev", function()
+ it("should load",function()
+ require("rng")
+ end)
+ it("should have a function to generate a random string",function()
+ local rng = require("rng")
+ assert(rng.randomstring)
+ assert(type(rng.randomstring) == "function")
+ end)
+ it("should generate a string",function()
+ local rng = require("rng")
+ local s = rng.randomstring("a",5)
+ assert(#s == 5)
+ assert(s == string.rep("a",5))
+ end)
+ it("should generate a string of the right length", function()
+ local rng = require("rng")
+ for i = 1,5 do
+ local ranlength = math.random(100)
+ local s = rng.randomstring("a",ranlength)
+ assert(#s == ranlength)
+ assert(s == string.rep("a",ranlength))
+ end
+ end)
+end)
diff --git a/src/controller.moon b/src/controller.moon
new file mode 100644
index 0000000..b86062e
--- /dev/null
+++ b/src/controller.moon
@@ -0,0 +1,16 @@
+
+ui = require("ui")
+
+controller = {}
+
+am.eval_js(require("controller_bridge"))
+
+controller.pump = () ->
+ print("pumping controller")
+ am.eval_js("CONT.loop()")
+ state = am.eval_js("CONT.last_state")
+ print(state)
+
+
+controller
+
diff --git a/src/controller_bridge.js b/src/controller_bridge.js
new file mode 100644
index 0000000..84bc040
--- /dev/null
+++ b/src/controller_bridge.js
@@ -0,0 +1,36 @@
+
+window.addEventListener("gamepadconnected", function(e) {
+ console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
+ e.gamepad.index,
+ e.gamepad.id,
+ e.gamepad.buttons.length,
+ e.gamepad.axes.length
+ );
+ var i;
+ for(i = 0; i < e.gamepad.buttons.length; i++){
+ CONT.last_state.buttons[i] = false;
+ }
+ for(i = 0; i < e.gamepad.axes.length; i++){
+ CONT.last_state.axes[i] = 0;
+ }
+ CONT.gp = navigator.getGamepads()[0];
+});
+
+window.CONT = {
+ messages: [],
+ gp: null,
+ last_state: {
+ buttons: [],
+ axes: []
+ },
+ loop: function() {
+ var i;
+ if(CONT.gp == null) return;
+ for(i = 0; i < CONT.gp.buttons.length; i++){
+ CONT.last_state.buttons[i] = CONT.gp.buttons[i].pressed;
+ }
+ for(i = 0; i< CONT.gp.axes.length; i++){
+ CONT.last_state.axes[i] = CONT.gp.axes[i];
+ }
+ }
+};
diff --git a/src/controller_test.moon b/src/controller_test.moon
new file mode 100644
index 0000000..45d1dcb
--- /dev/null
+++ b/src/controller_test.moon
@@ -0,0 +1,8 @@
+controller = require("controller")
+ui = require("ui")
+
+button = ui.button(0,0,100,100,"pump")
+button.on = () =>
+ controller.pump!
+
+
diff --git a/src/js_bridge.js b/src/js_bridge.js
index 0b5ba08..ecbbd70 100644
--- a/src/js_bridge.js
+++ b/src/js_bridge.js
@@ -10,8 +10,10 @@ function genRanHex(size) {
window.PEER = {
event_queue: [],
peers: {},
- message_queue: [],
+ peer_message_queue: [],
+ connection_message_queue: [],
connections: {},
+ to_connect: [], // Sometimes we have to wait a tick before the connection is ready.
create: function(tbl) {
var name = tbl.name;
var options = tbl.options;
@@ -19,6 +21,11 @@ window.PEER = {
var peer = new Peer(name, options);
PEER.peers[name] = peer;
},
+ delete_peer: function(tbl) {
+ var name = tbl.name;
+ console.log("[JS] Deleting peer " + name);
+ delete PEER.peers[name];
+ },
on: function(tbl) {
var name = tbl.name;
var e = tbl.e;
@@ -27,10 +34,12 @@ window.PEER = {
PEER.peers[name].on(e, function(data) {
console.log("[JS] Peer " + name + " received " + e);
if(e == "connection"){
- PEER.connections[[name,data.peer]] = data;
+ PEER.connections[[name,data.peer].sort()] = data;
+ console.log("[JS] Peer.connections is now");
+ console.log(PEER.connections);
data = [name,data.peer]; // rewrite connections
}
- PEER.message_queue.push({"message":message, "data":{
+ PEER.peer_message_queue.push({"message":message, "data":{
"call": "on",
"peer": name,
"e": e,
@@ -39,12 +48,17 @@ window.PEER = {
});
},
connect: function(tbl) {
- var name = tbl.name;
- var id = tbl.id;
- console.log("[JS] connecting " + name + " to " + id);
- var conn = PEER.peers[name].connect(id);
- PEER.connections[[name,id]] = conn;
- return [name,id];
+ var source = tbl.source;
+ var dest = tbl.dest;
+ console.log("[JS] connecting " + source + " to " + dest);
+ var conn = PEER.peers[source].connect(dest);
+ PEER.connections[[source,dest].sort()] = conn;
+ // Send a hello to always establish a connection
+ console.log("[JS] sending hello");// doesn't seem to show up in the output, but its needed so we don't drop the first message.
+ conn.send("Hello");
+ console.log("[JS] Connect called, PEER.connections is");
+ console.log(PEER.connections);
+ return [source,dest];
},
disconnect: function(tbl) {
PEER.peers[tbl.name].disconnect();
@@ -56,13 +70,13 @@ window.PEER = {
PEER.peers[tbl.name].destroy();
},
send: function(tbl){
- var name = tbl.name;
- var id = tbl.id;
+ var source = tbl.source;
+ var dest = tbl.dest;
var data = tbl.data;
- console.log("[JS] " + name + " is sending " + data + " to " + id);
- console.log(PEER.connections[[name,id]]);
+ console.log("[JS] " + source + " is sending " + data + " to " + dest);
+ console.log(PEER.connections[[source,dest].sort()]);
console.log(data);
- PEER.connections[[name,id]].send(data);
+ PEER.connections[[source,dest].sort()].send(data);
},
close: function(tbl){
var name = tbl.name;
@@ -70,20 +84,28 @@ window.PEER = {
PEER.connections[[name,id]].close();
},
conn_on: function(tbl){
- var name = tbl.name;
- var id = tbl.id;
+ var source = tbl.source;
+ var dest = tbl.dest;
var e = tbl.e;
var message = tbl.message;
- console.log("[JS] Setting hook for [" + name + "," + id + "] " + e + "," + message);
- PEER.connections[[name,id]].on(e, function(c) {
- console.log("[JS] connection between " + name + " and " + id + " received " + e);
- PEER.message_queue.push({"message":message, "data":{
+ console.log("[JS] Setting hook for [" + dest + "," + source + "] " + e + "," + message);
+ console.log(PEER.connections[[dest,source]]);
+ console.log(PEER.connections);
+ PEER.connections[[dest,source].sort()].on(e, function(c) {
+ console.log("[JS] connection between " + dest + " and " + source + " received " + e);
+ PEER.connection_message_queue.push({"message":message, "data":{
"call": "on",
- "peer": name,
- "id": id,
+ "peer": source,
+ "dest": dest,
"e": e,
"data": c
}});
});
},
+ conn_field: function(tbl){
+ var name = tbl.name;
+ var id = tbl.id;
+ var field = tbl.field;
+ return PEER.connections[[name,id].sort()][field];
+ }
};
diff --git a/src/main.lua b/src/main.lua
index 55b7ef5..9f55c43 100644
--- a/src/main.lua
+++ b/src/main.lua
@@ -9,9 +9,14 @@ local ui = require("ui")
win.scene:append(ui.node)
--require("world_test")
--require("net_test")
-require("ui_test")
+--require("ui_test")
+--require("router_test")
+require("controller_test")
require("log").observe(function(chunk)
+ if chunk.tags.ui then
+ return
+ end
print(table.concat({"[",chunk.level:upper(),"]",os.date()," > ",chunk.message}))
end)
--am.eval_js(require("js_bridge"))
diff --git a/src/net.moon b/src/net.moon
index b22d6f2..acd75c7 100644
--- a/src/net.moon
+++ b/src/net.moon
@@ -1,54 +1,103 @@
-- Handles the bridge to javascript to do peer-to-peer connections
-
log = require("log")
+rng = require("rng")
+util = require("util")
+
net = {}
-net.initalize = () ->
+initalized = false
+initalize = () ->
am.eval_js(require("js_bridge"))
+ initalized = true
net.call = (method, args) ->
- print("About to eval")
+ if not initalized
+ initalize!
result = am.eval_js("window.PEER." .. method .. "(" .. am.to_json(args) .. ")")
- print("Done evaling")
result
-net.pull = () ->
- messages = am.eval_js("window.PEER.message_queue")
- am.eval_js("window.PEER.message_queue = []")
+net.pull_peers = () ->
+ if not initalized
+ initalize!
+ messages = am.eval_js("window.PEER.peer_message_queue")
+ am.eval_js("window.PEER.peer_message_queue = []")
+ messages
+
+net.pull_connections = () ->
+ if not initalized
+ initalize!
+ messages = am.eval_js("window.PEER.connection_message_queue")
+ am.eval_js("window.PEER.connection_message_queue = []")
messages
callbacks = {}
peers = {}
connections = {}
+--Connections are always create js side, this is just it's lua representation
class Connection
+ @connections = {}
+ @methods = util.reverse({"data","open","close","error"})
new: (source, dest) =>
@source = source
@dest = dest
+ @get: (source, dest) =>
+ key = table.concat({source,dest},",")
+ if @connections[key]
+ return @connections[key]
+ @@connections[key] = Connection(source,dest)
+ @@connections[key]
on: (event, callback) =>
+ if not @@methods[event]
+ error("Tried to set an unknown event (" .. event .. ") on a connection")
newid = #callbacks + 1
callbacks[newid] = callback
- net.call("conn_on", {name: @source, id: @dest, e: event, message: newid})
+ while am.eval_js('window.PEER.connections[["' .. @source .. '","' .. @dest .. '"].sort()] == null')
+ print("Waiting for peer")
+ coroutine.yield("Waiting for peer")
+ --not backwards, "on" is always called on the incomming connection
+ net.call("conn_on", {source: @dest, dest: @source, e: event, message: newid})
send: (msgname, msg) =>
net.validate(msgname, msg)
- net.call("send",{name: @source, id: @dest, data: msg})
+ net.call("send",{source: @source, dest: @dest, data: {msgname, msg}})
class Peer
+ @methods = util.reverse({"open","connection","call","close","disconnected","error"})
new: (id) =>
- net.call("create",{name: id})
- if id
- @id = id
- peers[id] = @
+ -- We can't create peers with peerjs-generated random ids,
+ -- their names are too hard to type.
+ -- Instead use (Year, Month), and append 4 random digits
+ -- to make a unique id (people can't join past midnight at the
+ -- end of the month, oh well)
+ -- Also needs to handle id-already-taken errors.
+ id = id or @generate_id!
+ while peers[id]
+ id = @generate_id!
+ @id = id
+ peers[@id] = @
+ net.call("create",{name: @id})
+ log.info("Creating peer: " .. @id, {"net"})
+ generate_id: () =>
+ os.date("%Y%e") .. rng.randomstring("ab",1) --.. rng.numstring(4)
+ replace_id: () =>
+ log.info("Regenerating id for peer: " .. @id, {"net"})
+ -- peers[@id] = nil TODO: uncomment, this breaks when running multiple peers from the same tab.
+ net.call("delete_peer",{name: @id})
+ @id = @generate_id!
+ peers[@id] = @
+ net.call("create", {name: @id})
on: (event, callback) =>
+ if not @@methods[event]
+ error("Tried to set an unknown event (" .. event .. ") on a peer.")
newid = #callbacks + 1
callbacks[newid] = callback
net.call("on",{name: @id, message:newid, e: event})
connect: (id, options) =>
- conn = net.call("connect", {name: @id, id: id})
+ conn = net.call("connect", {source: @id, dest: id})
log.info("Got connection: " .. tostring(conn), {"net"})
- Connection(conn[1],conn[2])
+ Connection\get(conn[1],conn[2])
net.Peer = Peer
@@ -84,9 +133,10 @@ net.register_message = (name, format) ->
assert(type(format) == "table", "Format must be a table")
format.required = format.required or {}
format.optional = format.optional or {}
- assert(next(format.required) or next(format.optional), "No fields found")
+ if not (next(format.required) or next(format.optional))
+ log.warn("Message " .. name .. " registered with no fields.")
for set in *({format.required, format.optional})
- for field, type_ in pairs(format.required)
+ for field, type_ in pairs(set)
if type(type_) == "string"
key = string.format("%s\0%s\0%s",name,field,type_)
if not formatcache[key]
@@ -97,6 +147,7 @@ net.register_message = (name, format) ->
message_callbacks[name] = {}
net.validate = (name, message) ->
+ log.info("Validating message:" .. tostring(message), {"net"})
assert(type(message) == "table", "Message must be a table")
format = messages[name]
required = {}
@@ -122,27 +173,48 @@ net.listen = (name, callback, id) ->
net.defen = (name, id) ->
message_callbacks[name][id] = nil
+-- net.route = (conn, name, data) ->
+-- if message_callbacks[name]
+-- for id, callback in pairs(message_callbacks[name])
+-- ret = message_callbacks[name](conn,
+
rewrite_events = {
connection: (message) ->
- message.data.data = Connection(message.data.data[1], message.data.data[2])
+ conn = Connection\get(message.data.data[2], message.data.data[1])
+ assert(conn, "Failed to build conn?")
+ assert(conn.source and conn.dest)
+ message.data.data = conn
}
net.pump = () ->
- msg_ = net.pull!
- log.info("Processing " .. tostring(#msg_) .. " messages", {"net"})
+ msg_ = net.pull_peers!
+ log.info("Processing " .. tostring(#msg_) .. " peer messages", {"net"})
for message in *msg_
log.info(tostring(message), {"net", message.data.peer})
if rewrite_events[message.data.e]
log.info("Rewriting data due to " .. message.data.e .. " event", {"net", message.data.peer})
rewrite_events[message.data.e](message)
log.info(tostring(message), {"net", message.data.peer})
+ if not message.data.peer and message.data.e == "open"
+ log.info("Setting peerid for a peer that didn't have one " ..tostring(message), {"net"})
peer = peers[message.data.peer]
- assert(peer, "Failed to find peer:" .. message.data.peer)
+ assert(peer, "Failed to find peer:" .. message.data.peer .. " peers:" .. tostring(net.peers!))
callback = callbacks[message.message]
assert(callback, "Failed to find callback " .. message.message .. " on peer " .. message.data.peer)
callback(peer,message.data)
+ msg_ = net.pull_connections!
+ log.info("Processing " .. tostring(#msg_) .. " connection messages", {"net"})
+ for message in *msg_
+ log.info(tostring(message), {"net", message.data.peer})
+ connection = Connection\get(message.dest, message.peer)
+ callback = callbacks[message.message]
+ assert(callback, "Fakled to find callback " .. message.message .. " for message" .. tostring(message))
+ callback(connection, message.data)
+
net.peers = () ->
peers
+initalize!
+
net
diff --git a/src/net_test.moon b/src/net_test.moon
index c4989ab..25d79ca 100644
--- a/src/net_test.moon
+++ b/src/net_test.moon
@@ -6,7 +6,6 @@ test_button = ui.button(0,0,100,100,"Test")
pull_button = ui.button(100,0,100,100,"Pull")
send_button = ui.button(200,0,100,100,"Send")
-net.initalize!
peer1, peer2, conn = nil, nil, nil
test_button.on = (e) =>
net.register_message("HelloRequest",{
@@ -28,7 +27,7 @@ test_button.on = (e) =>
print("Peer 1 received data!")
)
peer1\on("error",(data) =>
- print("Peer 1 error:", data)
+ error("Peer 1 error:" .. tostring(data))
)
peer1\on("connection",(message)=>
print("Peer1 connection:", message)
@@ -37,25 +36,36 @@ test_button.on = (e) =>
)
)
peer2 = net.Peer()
- peer2\on("open", (data) =>
- print("Peer2 opened",data)
- conn = peer2\connect("blah-blah3")
- conn\on("data",(data) =>
- print("Peer2 data:",data)
+ peer2_startup = (peer) ->
+ peer\on("open", (data) =>
+ print("Peer2 opened",data)
+ conn = peer\connect(peer1.id)
+ conn\on("data",(data) =>
+ print("Peer2 data:",data)
+ )
)
- )
- peer2\on("connection", (data) =>
- print("Peer2 connected", data)
- )
- peer2\on("error", (data) =>
- error("Net error: " .. tostring(data))
- )
- peer2\on("data", (data) =>
- print("Peer2 on data",data)
- )
+ peer\on("connection", (message) =>
+ print("Peer2 connected", message)
+ message.data\on("data",(data)=>
+ print("Peer2 received data:", data)
+ )
+ )
+ peer\on("error", (message) =>
+ print("peer2 error:", message)
+ if message.data.type == "unavailable-id"
+ peer\replace_id!
+ peer2_startup(peer)
+ return
+ error("Peer2 error: " .. tostring(data))
+ )
+ peer\on("data", (data) =>
+ print("Peer2 on data",data)
+ )
+ peer2_startup(peer2)
pull_button.on = (e) =>
net.pump!
+ print("Peers:" .. tostring(net.peers!))
send_button.on = (e) =>
conn\send("HelloRequest",{
diff --git a/src/preload.lua b/src/preload.lua
index 8610cbc..ca2dc9a 100644
--- a/src/preload.lua
+++ b/src/preload.lua
@@ -7,22 +7,22 @@ local old_traceback = debug.traceback
debug.traceback = function(...)
local orig_traceback = old_traceback(...)
local noprint = {}
- return orig_traceback:gsub("([ \t]*)([%w/_]+%.lua):(%d+):([^\n]*)",function(spaces, filename, linenum, rest)
+ return orig_traceback:gsub("([%w/_]+%.lua):(%d+):",function(filename, linenum)
--If our file is not moonscript, we won't have a debug file
local debugfile = am.load_string(filename .. ".X")
if not debugfile then
- return spaces .. filename .. ":" .. linenum .. ":" .. rest
+ return filename .. ":" .. linenum .. ":"
end
debugfile = debugfile .. "\n"
for line in debugfile:gmatch("([^\n]+)\n") do
- _,_,pos,lua,moon = line:find("(%d+)%s+(%d+):%b[] >> (%d+)")
+ local _,_,pos,lua,moon = line:find("(%d+)%s+(%d+):%b[] >> (%d+)")
if pos and lua and moon and tonumber(linenum) == tonumber(lua) then
filename = filename:gsub(".lua$",".moon")
linenum = moon
break
end
end
- return string.format("%s%s:%d: %s", spaces, filename, linenum, rest)
+ return string.format("%s:%d:", filename, linenum)
end)
end
diff --git a/src/rng.moon b/src/rng.moon
new file mode 100644
index 0000000..d732386
--- /dev/null
+++ b/src/rng.moon
@@ -0,0 +1,30 @@
+
+rng = {}
+totally_random_seed = tonumber(os.date("%Y%H%M%S"))
+math.randomseed(totally_random_seed)
+
+rng.generator = (seed, m, n) ->
+ seed = seed or tonumber(os.date("%Y%S"))
+ co = coroutine.wrap(() ->
+ while true
+ math.randomseed(seed)
+ seed = math.random(m,n)
+ coroutine.yield(seed)
+ )
+ co, seed
+
+rng.randomstring = (charset, length) ->
+ t = {}
+ charset_len = #charset
+ for i = 1, length
+ char = math.random(charset_len)
+ t[i] = charset\sub(char,char)
+ table.concat(t)
+
+rng.hexstring = (length) ->
+ rng.randomstring("0123456789ABCDEF", length)
+
+rng.numstring = (length) ->
+ rng.randomstring("0123456789", length)
+
+rng
diff --git a/src/router.moon b/src/router.moon
new file mode 100644
index 0000000..7ef9bfc
--- /dev/null
+++ b/src/router.moon
@@ -0,0 +1,245 @@
+-- Network router, implements RAFT consensus with modificationes
+net = require("net")
+log = require("log")
+
+router = {}
+
+-- Election messages
+net.register_message("Prevote",{required:{peer:"string"}})
+net.register_message("SurveyElection",{})
+net.register_message("ResponseElection",{required:{candidate: "string"}})
+net.register_message("CompleteElection",{required:{elected: "string"}})
+
+-- Cluster state
+net.register_message("RequestClusterInfo",{})
+net.register_message("RespondClusterInfo",{required:{
+ peerlist: "table"
+ elected: "string"
+ term:"number"
+ prevotes: "table"
+}})
+
+net.register_message("RequestWorldInfo",{})
+net.register_message("RespondWorldInfo",{required:{
+ entities: "table"
+}})
+
+--Heartbeat
+net.register_message("RequestHeartbeat",{required:{n1:"number",n2:"number"}})
+net.register_message("RespondHeartbeat",{required:{n:"number",time:"number"}})
+
+--Suggestion/commit, use ("peer","entity","time") as key to find messages later to commit.
+-- -> peers can only suggest 1 state change per entity, per time
+--Suggestions are broadcast from peers
+net.register_message("Suggest",{required:{
+ entity:"number"
+ state:"table"
+ peer:"string"
+ time:"number"
+}})
+--Commits are broadcast from the elected peer
+net.register_message("Commit",{required:{
+ nonce: "number"
+ entity:"number"
+ peer:"string"
+ suggest_time:"number"
+ commit_time:"number"
+}})
+-- When a peer misses a committed message, re-request it
+net.register_message("RequestReplay",{required:{nonce: "number"}})
+net.register_message("RespondReplay",{required:{
+ nonce: "number"
+ entity: "number"
+ peer: "string"
+ state: "table"
+ suggest_time: "number"
+ commit_time: "number"
+}})
+--Rejections are also broadcast from the elected peer
+net.register_message("Reject",{required:{
+ nonce: "number"
+ entity:"number"
+ peer:"string"
+ suggest_time:"number"
+ reject_time:"number"
+}})
+--Suggest + commit from the elected peer, so we can throw away old state.
+net.register_message("Simplify",{required:{
+ entity:"number"
+ state:"table"
+ time:"number"
+}})
+
+--Testing
+net.register_message("Raw",{optional:{s:"string"}})
+
+class Queue
+ new: () =>
+ @queue = {}
+ push: (item) =>
+ table.insert(@queue, item)
+ pop: () =>
+ table.remove(@queue, 1)
+
+class Router
+ new: () =>
+ @peerlist = {}
+ @routes = {}
+
+ -- "uninitalized", "peer", "candidate", "elected"
+ @state = "unitialized"
+
+ -- [peerid] = "votes for peerid"
+ @prevotes = {}
+ @term = 0
+
+ -- The uncommited queue
+ @uncommited = Queue!
+
+ initalize: (id) =>
+ @set_route("RequestClusterInfo",(conn, message) =>
+ print("Requested cluster info:", message)
+ peerlist = {}
+ for peerid, _ in pairs(@peerlist)
+ table.insert(peerlist, peerid)
+ conn\send("RespondClusterInfo",{
+ peerlist: peerlist
+ elected: @elected
+ term: @term
+ prevotes: @prevotes
+ })
+ true
+ )
+ peer_setup = (peer) ->
+ peer\on("error",(message) =>
+ if message.data.type == "unavailable-id"
+ peer\replace_id!
+ peer_setup(peer)
+ return
+ error("Peer setup error: " .. tostring(message))
+ )
+ peer\on("open",(message) =>
+ print("Peer",peer.id, "opened:",message)
+ peer.open = true
+ )
+ peer = net.Peer!
+ @peer = peer
+ peer_setup(peer)
+ while not peer.open
+ coroutine.yield("Waiting for open")
+ net.pump!
+ peerlist = @peerlist
+ router = @
+ peer\on("connection",(message) =>
+ print("Peer",peer.id, "got connection", message)
+ assert(message.data.dest == peer.id)
+ peerlist[message.data.source] = message.data
+ message.data\send("Raw",{s: "Hello after conn"})
+ message.data\on("data",(datamsg) =>
+ print("Peer ",peer.id," got data:",datamsg)
+ router\route(message.data, datamsg.data[1], datamsg.data[2])
+ print("done routing")
+ )
+ message.data\on("error",(msg) =>
+ error(msg)
+ )
+ )
+ if id
+ print("Doing id fork")
+ connected = false
+ conn = peer\connect(id)
+ conn\on("open", (message) =>
+ print("Conn got open message")
+ connected = true
+ )
+ while not connected
+ coroutine.yield("Waiting for client to connect")
+ net.pump!
+ conn\on("error", (message) =>
+ error(message)
+ )
+ --Assume we vote for ourselves, and our peer votes for themselves
+ @prevotes[peer.id] = peer.id
+ @prevotes[id] = id
+ @state = "peer"
+ log.info("Peer passed, I'm a peer of " .. id, {"net"})
+ --while not connected
+ -- coroutine.yield("Give it a second to connect")
+ -- net.pump!
+ got_hello = false
+ conn\on("data", (message) =>
+ print("Peer got message:", message)
+ got_hello = true
+ )
+ while not got_hello
+ coroutine.yield("Waiting for hello")
+ net.pump!
+ @peerlist[peer.id] = peer
+ @peerlist[id] = conn
+ clusterinfo = @sync(conn, "RequestClusterInfo", {})
+ print("Got cluster info:", clusterinfo)
+
+ else
+ log.info("No peer passed, I'm the elected peer: " .. peer.id, {"net"})
+ @state = "elected"
+ @elected = peer.id
+ @term += 1
+ -- Vote for ourselves
+ @prevotes[peer.id] = peer.id
+ -- Add ourselves to the peerlist
+ @peerlist[peer.id] = peer
+ while true
+ coroutine.yield(peer.id)
+ net.pump!
+
+ sync: (conn, msgfmt, msg) =>
+ ret = nil
+ conn\on("data", (message) =>
+ ret = message
+ )
+ conn\send(msgfmt, msg)
+ while not ret
+ coroutine.yield("Waiting on " .. msgfmt)
+ net.pump!
+ return ret
+
+ broadcast: (msgfmt, message) =>
+ for peerid, conn in pairs(@peerlist)
+ if peerid ~= @peer.id
+ conn\send(msgfmt, message)
+
+ send_elected: (msgfmt, message) =>
+ elected_conn = @peerlist[@elected]
+ elected_conn\send(msgfmt, message)
+
+ set_route: (msgfmt, callback) =>
+ if @routes[msgfmt]
+ log.warn("Overwriting callback for message " .. msgfmt, {"net"})
+ @routes[msgfmt] = {callback}
+
+ listen: (msgfmt, callback, id) =>
+ id = id or {}
+ @routes[msgfmt] = @routes[msgfmt] or {}
+ @routes[msgfmt][id] = callback
+
+ defen: (msgfmt, id) =>
+ assert(@routes[msgfmt])
+ assert(@routes[msgfmt][id])
+ @routes[msgfmt][id] = nil
+
+ route: (conn, msgfmt, message) =>
+ print("Got route",conn,msgfmt,message)
+ assert(type(msgfmt) == "string", "Message format must be a string")
+ assert(type(message) == "table", "Message must be a table")
+ if @routes[msgfmt]
+ routed_any = false
+ for _, r in pairs(@routes[msgfmt])
+ routed_any = true
+ if r(@, conn, message)
+ break
+ if not routed_any
+ log.warn("No routes found for message format:" .. msgfmt, {"net"})
+ else
+ log.warn("No message callback registered for format " .. msgfmt .. " routes are: " .. tostring(@routes), {"net"})
+
+{:Router}
diff --git a/src/router_test.moon b/src/router_test.moon
new file mode 100644
index 0000000..e8d67c8
--- /dev/null
+++ b/src/router_test.moon
@@ -0,0 +1,49 @@
+
+import Router from require("router")
+ui = require("ui")
+
+button_client = ui.button(0,0,100,100,"Client")
+client_id = ui.textbox(0,100,100,32,"")
+button_server = ui.button(-300,0,100,100,"Server")
+co = nil
+button_client.on = () =>
+ if not co
+ co = coroutine.create((id) ->
+ router = Router!
+ print("Made router!")
+ router\initalize(id)
+ print("Finished initalize!")
+ router
+ )
+ print("Pumping... " .. coroutine.status(co))
+ if coroutine.status(co) ~= "dead"
+ succ, err = coroutine.resume(co, client_id.text.text)
+ if not succ
+ error(debug.traceback(co,err))
+ if type(err) == "string"
+ print(err)
+ @text.text = err
+ else
+ router = err
+ print("Got to the end of co!")
+button_server.on = () =>
+ if not co
+ co = coroutine.create(() ->
+ router = Router!
+ router\initalize!
+ router
+ )
+ print("Pumping... " .. coroutine.status(co))
+ if coroutine.status(co) ~= "dead"
+ succ, err = coroutine.resume(co)
+ if not succ
+ error(debug.traceback(co,err))
+ if err
+ if coroutine.status(co) ~= "dead"
+ print(err)
+ @text.text = err
+ else
+ print("Got router", err)
+ @text.text = err.peer.id
+
+
diff --git a/src/ui.moon b/src/ui.moon
index 7a06522..4746302 100644
--- a/src/ui.moon
+++ b/src/ui.moon
@@ -1,5 +1,6 @@
hc = require("party.hardoncollider.init")
win = require("window")
+log = require("log")
Button = require("ui.button")
Joystick = require("ui.joystick")
Textbox = require("ui.textbox")
@@ -29,6 +30,7 @@ ui.joystick = (x,y,r) ->
ui.node\append(joystick.node)
bounds = ui_world\circle(x,y,r)
ui.events.touch[bounds] = joystick
+ joystick
ui.textbox = (x,y,width,height,value,placeholder) ->
value = value or ""
@@ -38,6 +40,7 @@ ui.textbox = (x,y,width,height,value,placeholder) ->
bounds = ui_world\rectangle(x,y,width,height)
ui.events.mouse[bounds] = textbox
ui.events.keyboard[textbox] = true
+ textbox
ui.node = am.group!
@@ -69,10 +72,13 @@ ui.node\action(() ->
match = ui.events.mouse[collider]
if match
has_fire(match)
+ --log.info("Found button under mouse:" .. tostring(match), {"ui","mouseover"})
match\fire(mo_tbl)
if down
+ log.info("Found button under mouse:" .. tostring(match), {"ui","mousedown"})
match\fire(md_tbl)
if up
+ log.info("Found button under mouse:" .. tostring(match), {"ui","mouseup"})
match\fire(mu_tbl)
if math.length(wheel) > 0
etbl =
diff --git a/src/ui/button.moon b/src/ui/button.moon
index 789ecae..5915bc9 100644
--- a/src/ui/button.moon
+++ b/src/ui/button.moon
@@ -65,10 +65,6 @@ class Button
am.translate(w - @@up_lower_right.width, -(h - @@up_lower_right.height))\append(
@@up_lower_right
))
- @up_sprites\append(
- am.translate(@@up_upper_left.width, -@@up_upper_right.height)\append(
- am.text(text, "left","top", color.am_color.foreground)
- ))
@down_sprites\append(@@down_upper_left)
@down_sprites\append(
am.translate(@@down_upper_left.width,0)\append(
diff --git a/src/ui/textbox.moon b/src/ui/textbox.moon
index 3c6151d..c1bd521 100644
--- a/src/ui/textbox.moon
+++ b/src/ui/textbox.moon
@@ -65,7 +65,6 @@ class Textbox extends Button
update_cursor_pos: () =>
@.cursor("translate").x = @cursor_pos * 9
fire: (e) =>
- print("cursor pos is", @cursor_pos)
if e.event == "mouse_down"
@down!
if @on
@@ -74,7 +73,6 @@ class Textbox extends Button
if add_key
t = @text.text
for key in *e.data
- print("analyzing key:",key)
if key == "delete" or key == "backspace"
@cursor_pos -=1
if @cursor_pos < 0
diff --git a/src/util.lua b/src/util.lua
index 8fccab2..52597b2 100644
--- a/src/util.lua
+++ b/src/util.lua
@@ -81,4 +81,13 @@ function tostring(el)
return old_tostring(el)
end
+function util.reverse(tbl, val)
+ val = val or true
+ local ret = {}
+ for _,v in ipairs(tbl) do
+ ret[v] = val
+ end
+ return ret
+end
+
return util
diff --git a/src/world.moon b/src/world.moon
index d511e88..3e4e09a 100644
--- a/src/world.moon
+++ b/src/world.moon
@@ -6,13 +6,12 @@ ecs = require("ecs")
--Use a collider to decide what to render
x = {
world_e: ecs.Entity(1)
- world_x: 0
+ -- local offsets from the world
+ world_x: 0
world_y: 0
+ -- Have we selected an input type yet?
controller_selected: false
- hosting: false
- peer_channels: {}
- network_proposed: {}
- network_commited: {}
+ -- Level information
level: {
graphics:{}
entities:{}