896 lines
21 KiB
JavaScript
896 lines
21 KiB
JavaScript
(function engine() {
|
|
prosperon.DOC = Symbol('+documentation+') // Symbol for documentation references
|
|
|
|
prosperon.id = 'newguy';
|
|
|
|
var listeners = new Map()
|
|
|
|
prosperon.on = function(type, callback) {
|
|
if (!listeners.has(type)) listeners.set(type, [])
|
|
listeners.get(type).push(callback)
|
|
|
|
return function() {
|
|
var arr = listeners.get(type)
|
|
if (!arr) return
|
|
var idx = arr.indexOf(callback)
|
|
if (idx >= 0) arr.splice(idx,1)
|
|
}
|
|
}
|
|
|
|
prosperon.dispatch = function(type, data) {
|
|
var arr = listeners.get(type)
|
|
if (!arr) return
|
|
for (var callback of arr) callback(data)
|
|
}
|
|
|
|
var os = use_embed('os')
|
|
var js = use_embed('js')
|
|
|
|
prosperon.on('SIGINT', function() {
|
|
os.exit(1)
|
|
})
|
|
|
|
prosperon.on('SIGABRT', function() {
|
|
console.error(new Error('SIGABRT'))
|
|
os.exit(1)
|
|
})
|
|
|
|
prosperon.on('SIGSEGV', function() {
|
|
console.error(new Error('SIGSEGV'))
|
|
os.exit(1)
|
|
})
|
|
|
|
var use_cache = {}
|
|
|
|
Object.defineProperty(Function.prototype, "hashify", {
|
|
value: function () {
|
|
var hash = new Map()
|
|
var fn = this
|
|
function hashified(...args) {
|
|
var key = args[0]
|
|
if (!hash.has(key)) hash.set(key, fn(...args))
|
|
return hash.get(key)
|
|
}
|
|
return hashified
|
|
},
|
|
})
|
|
|
|
var io = use_embed('io')
|
|
var tracy = use_embed('tracy')
|
|
|
|
var RESPATH = 'scripts/modules/resources.js'
|
|
var canonical = io.realdir(RESPATH) + 'resources.js'
|
|
var content = io.slurp(RESPATH)
|
|
var resources = js.eval(RESPATH, `(function setup_resources(){${content}})`).call({})
|
|
|
|
use_cache[resources.canonical('resources.js')] = resources
|
|
|
|
function print_api(obj) {
|
|
for (var prop in obj) {
|
|
if (!obj.hasOwnProperty(prop)) continue
|
|
var val = obj[prop]
|
|
console.log(prop)
|
|
if (typeof val === 'function') {
|
|
var m = val.toString().match(/\(([^)]*)\)/)
|
|
if (m) console.log(' function: ' + prop + '(' + m[1].trim() + ')')
|
|
}
|
|
}
|
|
}
|
|
|
|
prosperon.PATH = [
|
|
"/",
|
|
"scripts/modules/",
|
|
"scripts/modules/ext/",
|
|
]
|
|
|
|
// path is the path of a module or script to resolve
|
|
var script_fn = function script_fn(path) {
|
|
var parsed = {}
|
|
var file = resources.find_script(path)
|
|
|
|
if (!file) {
|
|
parsed.module_ret = bare_load(path)
|
|
if (!parsed.module_ret) throw new Error(`Module ${path} could not be created`)
|
|
return parsed
|
|
}
|
|
|
|
var content = io.slurp(file)
|
|
var parsed = parse_file(content, file)
|
|
var module_name = file.name()
|
|
parsed.module_ret = bare_load(path)
|
|
parsed.module_ret ??= {}
|
|
|
|
if (parsed.module) {
|
|
var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; ${parsed.module}})`
|
|
var module_fn = js.eval(file, mod_script)
|
|
parsed.module_ret = module_fn.call(parsed.module_ret)
|
|
if (parsed.module_ret === undefined || parsed.module_ret === null)
|
|
throw new Error(`Module ${module_name} must return a value`)
|
|
parsed.module_fn = module_fn
|
|
}
|
|
|
|
parsed.program ??= ""
|
|
var prog_script = `(function use_${module_name}() { var self = this; var $ = this.__proto__; ${parsed.program}})`
|
|
parsed.prog_fn = js.eval(file, prog_script)
|
|
|
|
return parsed
|
|
}.hashify()
|
|
|
|
function bare_load(file) {
|
|
try {
|
|
return use_embed(file)
|
|
} catch (e) { }
|
|
try {
|
|
return use_dyn(file + so_ext)
|
|
} catch(e) { }
|
|
return undefined
|
|
}
|
|
|
|
var res_cache = {}
|
|
|
|
function console_rec(category, priority, line, file, msg) {
|
|
return `[${prosperon.id.substring(0,6)}] ${file}:${line}: [${category} ${priority}]: ${msg}\n`
|
|
}
|
|
|
|
io.mkdir('.prosperon')
|
|
var logfile = io.open('.prosperon/log.txt')
|
|
|
|
function pprint(msg, lvl = 0) {
|
|
if (!logfile) return
|
|
|
|
if (typeof msg === "object") msg = JSON.stringify(msg, null, 2)
|
|
|
|
var file = "nofile"
|
|
var line = 0
|
|
|
|
var caller = new Error().stack.split("\n")[2]
|
|
if (caller) {
|
|
var md = caller.match(/\((.*)\:/)
|
|
var m = md ? md[1] : "SCRIPT"
|
|
if (m) file = m
|
|
md = caller.match(/\:(\d*)\)/)
|
|
m = md ? md[1] : 0
|
|
if (m) line = m
|
|
}
|
|
var fmt = console_rec("script", lvl, line, file, msg)
|
|
console.print(fmt)
|
|
|
|
if (logfile)
|
|
logfile.write(fmt)
|
|
|
|
if (tracy) tracy.message(fmt)
|
|
}
|
|
|
|
console.spam = function(msg) {
|
|
pprint(msg, 0)
|
|
}
|
|
console.debug = function(msg) {
|
|
pprint(msg, 1)
|
|
}
|
|
console.info = function(msg) {
|
|
pprint(msg, 2)
|
|
}
|
|
console.warn = function(msg) {
|
|
pprint(msg, 3)
|
|
}
|
|
console.log = function(msg) {
|
|
pprint(msg, 2)
|
|
}
|
|
console.error = function(e) {
|
|
if (!e)
|
|
e = new Error()
|
|
|
|
if (e instanceof Error)
|
|
pprint(`${e.name} : ${e.message}
|
|
${e.stack}`, 4)
|
|
else
|
|
pprint(e,4)
|
|
}
|
|
console.panic = function(e) {
|
|
pprint(e, 5)
|
|
os.quit()
|
|
}
|
|
console.assert = function(op, str = `assertion failed [value '${op}']`) {
|
|
if (!op) console.panic(str)
|
|
}
|
|
|
|
os.on('uncaught_exception', function(e) { console.error(e) })
|
|
|
|
console[prosperon.DOC] = {
|
|
doc: "The console object provides various logging, debugging, and output methods.",
|
|
spam: "Output a spam-level message for very verbose logging.",
|
|
debug: "Output a debug-level message.",
|
|
info: "Output info level message.",
|
|
warn: "Output warn level message.",
|
|
error: "Output error level message, and print stacktrace.",
|
|
panic: "Output a panic-level message and exit the program.",
|
|
assert: "If the condition is false, print an error and panic.",
|
|
critical: "Output critical level message, and exit game immediately.",
|
|
write: "Write raw text to console.",
|
|
say: "Write raw text to console, plus a newline.",
|
|
log: "Output directly to in game console.",
|
|
level: "Set level to output logging to console.",
|
|
stack: "Output a stacktrace to console.",
|
|
clear: "Clear console."
|
|
}
|
|
|
|
var BASEPATH = 'scripts/core/base.js'
|
|
var script = io.slurp(BASEPATH)
|
|
var fnname = "base"
|
|
script = `(function ${fnname}() { ${script}; })`
|
|
js.eval(BASEPATH, script)()
|
|
|
|
function add_timer(obj, fn, seconds) {
|
|
var timers = obj[TIMERS]
|
|
|
|
var stop = function () {
|
|
if (!timer) return
|
|
timers.delete(stop)
|
|
timer.fn = undefined
|
|
timer = undefined
|
|
}
|
|
|
|
function execute() {
|
|
if (fn) timer.remain = fn(stop.seconds)
|
|
if (!timer) return
|
|
if (!timer.remain) stop()
|
|
else stop.seconds = timer.remain
|
|
}
|
|
|
|
var timer = os.make_timer(execute)
|
|
timer.remain = seconds
|
|
|
|
stop.remain = seconds
|
|
stop.seconds = seconds
|
|
|
|
timers.push(stop)
|
|
return stop
|
|
}
|
|
|
|
var DEAD = Symbol()
|
|
var GARBAGE = Symbol()
|
|
var FILE = Symbol()
|
|
var TIMERS = Symbol()
|
|
var REGGIES = Symbol()
|
|
var UNDERLINGS = Symbol()
|
|
var OVERLING = Symbol()
|
|
|
|
var actor = {}
|
|
globalThis.actor = actor
|
|
|
|
var so_ext
|
|
switch(os.platform()) {
|
|
case 'Windows':
|
|
so_ext = '.dll'
|
|
break
|
|
default:
|
|
so_ext = '.so'
|
|
break
|
|
}
|
|
|
|
globalThis.use = function use(file) {
|
|
if (use_cache[file]) return use_cache[file]
|
|
var mod = script_fn(file)
|
|
use_cache[file] = mod.module_ret
|
|
return use_cache[file]
|
|
}
|
|
|
|
globalThis.json = use('json')
|
|
|
|
function parse_file(content, file) {
|
|
if (!content) return {}
|
|
var parts = content.split(/\n\s*---\s*\n/)
|
|
if (parts.length === 1) {
|
|
var part = parts[0]
|
|
if (part.match(/return\s+[^;]+;?\s*$/))
|
|
return { module: part }
|
|
return { program: part }
|
|
}
|
|
var module = parts[0]
|
|
if (!/\breturn\b/.test(module))
|
|
throw new Error(`Malformed file: ${file}. Module section must end with a return statement.`);
|
|
|
|
try {
|
|
new Function(module)()
|
|
} catch (e) {
|
|
throw new Error(`Malformed file: ${file}. Module section must end with a return statement.\n` + e.message);
|
|
}
|
|
|
|
var pad = '\n'.repeat(module.split('\n').length+4)
|
|
return {
|
|
module,
|
|
program: pad+parts[1]
|
|
}
|
|
}
|
|
|
|
globalThis.Register = {
|
|
registries: [],
|
|
|
|
add_cb(name) {
|
|
var n = {}
|
|
var fns = []
|
|
|
|
n.register = function (fn, oname) {
|
|
if (typeof fn !== 'function') return
|
|
|
|
var dofn = function (...args) {
|
|
fn(...args)
|
|
}
|
|
Object.defineProperty(dofn, 'name', {value:`do_${oname}`})
|
|
|
|
var left = 0
|
|
var right = fns.length - 1
|
|
dofn.layer = fn.layer
|
|
dofn.layer ??= 0
|
|
|
|
while (left <= right) {
|
|
var mid = Math.floor((left + right) / 2)
|
|
if (fns[mid] === dofn.layer) {
|
|
left = mid
|
|
break
|
|
} else if (fns[mid].layer < dofn.layer) left = mid + 1
|
|
else right = mid - 1
|
|
}
|
|
|
|
fns.splice(left, 0, dofn)
|
|
return function () {
|
|
fns.delete(dofn)
|
|
}
|
|
}
|
|
|
|
prosperon[name] = function (...args) {
|
|
fns.forEach(fn => {
|
|
fn(...args)
|
|
})
|
|
}
|
|
Object.defineProperty(prosperon[name], 'name', {value:name})
|
|
prosperon[name].fns = fns
|
|
|
|
n.clear = function () {
|
|
fns = []
|
|
}
|
|
|
|
Register[name] = n
|
|
Register.registries[name] = n
|
|
return n
|
|
},
|
|
}
|
|
|
|
Register.pull_registers = function pull_registers(obj) {
|
|
var reggies = []
|
|
for (var reg in Register.registries) {
|
|
if (typeof obj[reg] === "function")
|
|
reggies.push(reg)
|
|
}
|
|
return reggies
|
|
}
|
|
|
|
Register.register_obj = function register_obj(obj, reg) {
|
|
var fn = obj[reg].bind(obj)
|
|
fn.layer = obj[reg].layer
|
|
var name = obj.ur ? obj.ur.name : obj.toString()
|
|
obj[TIMERS].push(Register.registries[reg].register(fn, name))
|
|
if (!obj[reg].name) Object.defineProperty(obj[reg], 'name', {value:`${obj._file}_${reg}`})
|
|
}
|
|
|
|
Register.check_registers = function check_registers(obj) {
|
|
if (obj[REGGIES]) {
|
|
if (obj[REGGIES].length == 0) return
|
|
for (var reg of obj[REGGIES])
|
|
Register.register_obj(obj,reg)
|
|
return
|
|
}
|
|
for (var reg in Register.registries) {
|
|
if (typeof obj[reg] === "function")
|
|
Register.register_obj(obj,reg)
|
|
}
|
|
}
|
|
|
|
Register.add_cb("appupdate")
|
|
Register.add_cb("update").doc = "Called once per frame."
|
|
Register.add_cb("physupdate")
|
|
Register.add_cb("gui")
|
|
Register.add_cb("hud")
|
|
Register.add_cb("draw")
|
|
Register.add_cb("imgui")
|
|
Register.add_cb("app")
|
|
|
|
function cant_kill() {
|
|
throw Error("Can't kill an object in its spawning code. Move the kill command to awake.")
|
|
}
|
|
|
|
actor.toString = function() { return this[FILE] }
|
|
|
|
actor.spawn = function spawn(script, config, callback) {
|
|
if (this[DEAD]) throw new Error("Attempting to spawn on a dead actor")
|
|
var prog
|
|
if (!script) {
|
|
prog = {}
|
|
prog.module_ret = {}
|
|
prog.prog_fn = function() {}
|
|
} else {
|
|
prog = script_fn(script)
|
|
if (!prog.prog_fn) throw new Error(`Script ${script} is not an actor script or has no actor component`)
|
|
}
|
|
|
|
var underling
|
|
prog.module_ret.__proto__ = actor
|
|
underling = Object.create(prog.module_ret)
|
|
underling[OVERLING] = this
|
|
underling[FILE] = script
|
|
underling[TIMERS] = []
|
|
underling[UNDERLINGS] = new Set()
|
|
|
|
Object.defineProperty(underling, 'overling', {
|
|
get() { return this[OVERLING] },
|
|
enumerable:true,
|
|
configurable:false
|
|
})
|
|
|
|
Object.defineProperty(underling, 'underlings', {
|
|
get() { return new Set(this[UNDERLINGS]) },
|
|
enumerable:true,
|
|
configurable:false
|
|
})
|
|
|
|
Object.defineProperty(underling, 'spawn', {
|
|
value: actor.spawn,
|
|
writable:false,
|
|
enumerable:true,
|
|
configurable:false
|
|
})
|
|
|
|
Object.defineProperty(underling, 'kill', {
|
|
value: actor.kill,
|
|
writable:false,
|
|
enumerable:true,
|
|
configurable:false
|
|
})
|
|
|
|
Object.defineProperty(underling, 'delay', {
|
|
value: actor.delay,
|
|
writable:false,
|
|
enumerable:true,
|
|
configurable:false
|
|
})
|
|
|
|
if (callback) callback(underling, { message:"created" })
|
|
|
|
try {
|
|
prog.prog_fn.call(underling)
|
|
} catch(e) { throw e }
|
|
|
|
if (underling[DEAD]) return undefined
|
|
|
|
if (typeof config === 'object') Object.assign(underling, config)
|
|
|
|
if (!underling[REGGIES])
|
|
underling.__proto__[REGGIES] = Register.pull_registers(underling)
|
|
|
|
Register.check_registers(underling)
|
|
if (underling.awake) underling.awake()
|
|
|
|
this[UNDERLINGS].add(underling)
|
|
if (underling.tag) act.tag_add(underling.tag, underling)
|
|
|
|
underling[GARBAGE] = underling.garbage
|
|
return underling
|
|
}
|
|
|
|
actor.clear = function actor_clear() {
|
|
this[UNDERLINGS].forEach(p => {
|
|
p.kill()
|
|
})
|
|
this[UNDERLINGS].clear()
|
|
}
|
|
|
|
var input = use('input')
|
|
|
|
actor.kill = function kill() {
|
|
if (this[DEAD]) return
|
|
this[DEAD] = true
|
|
this[TIMERS].slice().forEach(t => t())
|
|
delete this[TIMERS]
|
|
input.do_uncontrol(this)
|
|
|
|
this.clear()
|
|
this[OVERLING][UNDERLINGS].delete(this)
|
|
delete this[UNDERLINGS]
|
|
|
|
if (typeof this.garbage === "function") this.garbage()
|
|
if (typeof this.then === "function") this.then()
|
|
|
|
act.tag_clear_guid(this)
|
|
}
|
|
actor.kill.doc = `Remove this actor and all its underlings from existence.`
|
|
|
|
actor.delay = function(fn, seconds) {
|
|
if (this[DEAD]) return
|
|
add_timer(this, fn, seconds)
|
|
}
|
|
actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`
|
|
|
|
var act = use('actor')
|
|
actor[UNDERLINGS] = new Set()
|
|
|
|
globalThis.mixin("color")
|
|
|
|
var DOCPATH = 'scripts/core/doc.js'
|
|
var script = io.slurp(DOCPATH)
|
|
var fnname = "doc"
|
|
script = `(function ${fnname}() { ${script}; })`
|
|
js.eval(DOCPATH, script)()
|
|
|
|
var enet = use('enet')
|
|
var util = use('util')
|
|
var math = use('math')
|
|
var crypto = use('crypto')
|
|
|
|
var REPLY = Symbol()
|
|
var REPLYCC = Symbol()
|
|
var ACTORID = Symbol()
|
|
var PEER = Symbol()
|
|
|
|
var ar = 5 // seconds before reclamation
|
|
|
|
/*
|
|
When an actor object like $_ is passed to another machine or function, it appears like this
|
|
id: local to the machine. Currently, the local port it is listening on
|
|
address: the IP address of the portal that connects it, if set
|
|
port: the public port of the portal that connects it, if set
|
|
|
|
There is a map of actor.id -> peer, so messages can be sent.
|
|
*/
|
|
|
|
/*
|
|
Each actor has an id. When an actor object is received, that is essentially just an id. The method to reach the id is in routing tables local to the actor that has obtained the object. If actor.id === prosperon.id, it's localhost.
|
|
|
|
Currently, each actor has an enet peer, so we're focused on that. Eventually, there might also be a thread local mailbox.
|
|
*/
|
|
|
|
var $_ = {}
|
|
|
|
$_.random = crypto.random;
|
|
$_.clock = function(fn)
|
|
{
|
|
return os.now()
|
|
}
|
|
|
|
var underlings = new Set()
|
|
var overling = undefined
|
|
|
|
var host = enet.create_host({
|
|
address:"127.0.0.1",
|
|
port:0,
|
|
channels:0,
|
|
incoming_bandwidth:0,
|
|
outgoing_bandwidth:0
|
|
});
|
|
|
|
globalThis.$_ = $_
|
|
|
|
var receive_fn
|
|
|
|
var peers = {} // mapping of guids to peers
|
|
var greeters = {} // mapping of underling guids to their system callback functions
|
|
var peer2id = new WeakMap() // local bound peers to relevant id
|
|
|
|
$_.connection = function(callback, actor, config) {
|
|
var peer = peers[actor.id]
|
|
if (!peer) throw new Error(`Cannot get information from actor ${json.encode(actor)}`)
|
|
|
|
callback({
|
|
latency: peer.rtt,
|
|
bandwidth: {
|
|
incoming: peer.incoming_bandwidth,
|
|
outgoing: peer.outgoing_bandwidth
|
|
},
|
|
activity: {
|
|
last_sent: peer.last_send_time,
|
|
last_received: peer.last_receive_time,
|
|
},
|
|
mtu: peer.mtu,
|
|
data: {
|
|
incoming_total: peer.incoming_data_total,
|
|
outgoing_total: peer.outgoing_data_total,
|
|
reliable_in_transit: peer.reliable_data_in_transit
|
|
},
|
|
latency_variance: peer.rtt_variance,
|
|
packet_loss: peer.packet_loss,
|
|
state: peer.state
|
|
});
|
|
};
|
|
|
|
var portal = undefined
|
|
var pppfn
|
|
$_.portal = function(fn, port)
|
|
{
|
|
if (portal)
|
|
throw new Error(`Already started a portal listening on ${portal.port}`)
|
|
|
|
console.log(`starting a portal on port ${port}`)
|
|
if (!port)
|
|
throw new Error("Requires a valid port.");
|
|
|
|
portal = enet.create_host({
|
|
address: "any", // any can connect
|
|
port,
|
|
})
|
|
|
|
pppfn = fn
|
|
}
|
|
|
|
function portal_fn(e)
|
|
{
|
|
switch (e.type) {
|
|
case "connect":
|
|
console.log('portal got connect')
|
|
break
|
|
|
|
case "disconnect":
|
|
console.log('portal got disconnect')
|
|
break
|
|
|
|
case "receive":
|
|
pppfn(e.data)
|
|
break
|
|
}
|
|
}
|
|
|
|
var peer2contact = new WeakMap()
|
|
$_.contact = function(callback, record)
|
|
{
|
|
if (!callback) throw new Error('Contact requires a callback function')
|
|
console.log(`connecting to ${json.encode(record)}`)
|
|
var peer = host.connect(record.address, record.port)
|
|
peer2contact.set(peer, {
|
|
callback,
|
|
record
|
|
})
|
|
}
|
|
|
|
$_.receiver = function(fn)
|
|
{
|
|
receive_fn = fn;
|
|
}
|
|
|
|
$_.start = function(cb, prg, arg)
|
|
{
|
|
var id = util.guid()
|
|
greeters[id] = cb
|
|
var argv = [
|
|
"./prosperon",
|
|
"spawn",
|
|
"--overling", host.port,
|
|
"--id", id,
|
|
]
|
|
|
|
if (prg)
|
|
argv = argv.concat(['--program', prg])
|
|
|
|
if (arg)
|
|
argv = argv.concat(cmd.encode(arg))
|
|
|
|
os.createprocess(argv)
|
|
}
|
|
|
|
$_.stop = function(actor)
|
|
{
|
|
if (!actor)
|
|
destroyself()
|
|
|
|
actor_send(actor, {type:"stop"})
|
|
}
|
|
|
|
var unneeded_fn = $_.stop
|
|
var unneeded_time = ar
|
|
|
|
$_.unneeded = function(fn, seconds = ar)
|
|
{
|
|
if (typeof fn !== 'function') throw new Error ('Must supply a function to unneeded.')
|
|
unneeded_fn = fn
|
|
unneeded_time = seconds
|
|
}
|
|
|
|
$_.delay = function(fn, seconds)
|
|
{
|
|
var id = os.addtimer(fn, seconds);
|
|
return function() { os.removetimer(id); }
|
|
}
|
|
|
|
// Set of actor guids
|
|
var couplings = new Set()
|
|
|
|
$_.couple = function(actor)
|
|
{
|
|
couplings.add(actor.id)
|
|
}
|
|
|
|
// Shuffles the message to the actor with whatever means available
|
|
function actor_send(actor, message)
|
|
{
|
|
|
|
if (typeof message !== 'object')
|
|
throw new Error('Must send an object record.')
|
|
|
|
console.log(`Getting peer for ${actor.id}`)
|
|
var peer = peers[actor.id]
|
|
if (!peer) throw new Error(`Could not send message to actor.`)
|
|
console.log(`sending message ${json.encode(message)} to ${json.encode(actor)}`)
|
|
peer.send(message)
|
|
}
|
|
|
|
// Map of reply IDs to functions
|
|
var replies = {}
|
|
|
|
// Map of a message object to a peer for replying directly
|
|
var reply_cc = new WeakMap()
|
|
|
|
$_.send = function(actor, message, reply)
|
|
{
|
|
if (typeof message !== 'object')
|
|
throw new Error('Must send an object record.')
|
|
|
|
console.log(`sending to ${json.encode(actor)} ...`)
|
|
|
|
var send = {
|
|
type:"user",
|
|
data: message
|
|
}
|
|
|
|
if (actor[REPLYCC]) {
|
|
console.log(`replying to a message: ${json.encode(actor)}`)
|
|
console.log(`replycc and reply are ${actor[REPLYCC]} and ${actor[REPLY]}`)
|
|
actor.id = actor[REPLYCC]
|
|
send.return = actor[REPLY]
|
|
} else if (peers[actor.id]) {
|
|
} else
|
|
throw new Error(`Could not send message to actor.`)
|
|
|
|
if (reply) {
|
|
var id = util.guid()
|
|
replies[id] = reply
|
|
send.reply = id
|
|
}
|
|
|
|
actor_send(actor, send)
|
|
}
|
|
|
|
var cmd = use('cmd')
|
|
cmd.process(prosperon.argv)
|
|
|
|
if (!prosperon.args.id)
|
|
prosperon.id = util.guid()
|
|
else
|
|
prosperon.id = prosperon.args.id;
|
|
|
|
if (prosperon.args.overling)
|
|
host.connect("localhost", prosperon.args.overling)
|
|
|
|
if (prosperon.args.program)
|
|
actor.spawn(prosperon.args.program)
|
|
|
|
var unneeded_timer = $_.delay($_.stop, ar)
|
|
|
|
function destroyself()
|
|
{
|
|
console.log('got the message to destroy')
|
|
os.exit(0)
|
|
}
|
|
|
|
function handle_actor_disconnect(id)
|
|
{
|
|
if (couplings.has(id))
|
|
$_.stop()
|
|
|
|
delete peers[id]
|
|
delete greeters[id]
|
|
}
|
|
|
|
function handle_message(e)
|
|
{
|
|
console.log(`handling message ${json.encode(e)}`)
|
|
switch (e.data.type) {
|
|
case "user":
|
|
if (e.data.return) {
|
|
console.log(`Message has a return address.`)
|
|
var fn = replies[e.data.return]
|
|
if (!fn)
|
|
throw new Error(`Could not find return function for message ${e.data.return}`)
|
|
|
|
fn(e.data)
|
|
delete replies[e.data.return]
|
|
return
|
|
}
|
|
|
|
if (receive_fn) {
|
|
if (e.data.reply) {
|
|
// Doing this here because there is no way for an actor to reply to the message if it doesn't get it in the first place
|
|
e.data.data[REPLYCC] = peer2id.get(e.peer)
|
|
e.data.data[REPLY] = e.data.reply
|
|
console.log(`set replycc and reply to ${e.data.data[REPLYCC]} and ${e.data.data[REPLY]}`)
|
|
}
|
|
|
|
receive_fn(e.data.data)
|
|
}
|
|
break
|
|
|
|
case "greet":
|
|
peers[e.data.id] = e.peer;
|
|
peer2id.set(e.peer, e.data.id);
|
|
console.log(`Registered a peer with ${e.data.id}`)
|
|
var greeter = greeters[e.data.id]
|
|
if (!greeter) return; //throw new Error(`No greeter registered for greet message id ${e.data.id}`)
|
|
greeter({
|
|
type: "actor_started",
|
|
actor: { id: e.data.id }
|
|
})
|
|
break
|
|
|
|
case "stop":
|
|
destroyself()
|
|
break
|
|
|
|
case "contact":
|
|
if (pppfn) pppfn(e.data)
|
|
break
|
|
|
|
}
|
|
}
|
|
|
|
function handle_connect(e)
|
|
{
|
|
console.log(`message arrived at ${host.address}:${host.port} from somebody ... ${e.peer.address}:${e.peer.port}`)
|
|
var contact = peer2contact.get(e.peer)
|
|
if (contact) {
|
|
// We have successfully made contact. now send the request.
|
|
e.peer.send({
|
|
type: "contact",
|
|
data: contact.record
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// For a local greet
|
|
e.peer.send({
|
|
type:"greet",
|
|
id: prosperon.id
|
|
})
|
|
}
|
|
|
|
var hang = 0.001
|
|
while (1) {
|
|
os.waitevent(e => {
|
|
unneeded_timer()
|
|
unneeded_timer = $_.delay(unneeded_fn, unneeded_time)
|
|
}, hang)
|
|
|
|
host.service(e => {
|
|
unneeded_timer()
|
|
switch(e.type) {
|
|
case "connect":
|
|
handle_connect(e);
|
|
break;
|
|
|
|
case "receive":
|
|
handle_message(e)
|
|
break;
|
|
|
|
case "disconnect":
|
|
var id = peer2id(e.peer)
|
|
greeters[id]({
|
|
type: "actor_stopped"
|
|
});
|
|
handle_actor_disconnect(id);
|
|
break;
|
|
}
|
|
unneeded_timer = $_.delay(unneeded_fn, unneeded_time)
|
|
}, hang);
|
|
|
|
if (portal)
|
|
portal.service(portal_fn, hang)
|
|
}
|
|
|
|
})()
|