2021-04-29 20:19:56 -04:00
|
|
|
local random = random
|
|
|
|
|
2021-04-09 15:06:55 -04:00
|
|
|
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
|
|
|
|
2021-05-01 19:39:35 -04:00
|
|
|
local Promise = nil
|
|
|
|
|
|
|
|
local util = nil
|
2021-04-23 19:04:59 -04:00
|
|
|
local config = dofile(base.."/config.lua")
|
2021-04-23 15:39:11 -04:00
|
|
|
local serpent = dofile(base.."/serpent.lua")
|
2021-04-28 04:05:16 -04:00
|
|
|
|
2021-05-04 08:20:35 -04:00
|
|
|
local pipePrefix = "donk_runner_"..
|
2021-04-28 18:54:39 -04:00
|
|
|
string.hex(math.floor(random.integer(0, 0xffffffff)))..
|
|
|
|
string.hex(math.floor(random.integer(0, 0xffffffff)))
|
2021-04-09 15:06:55 -04:00
|
|
|
|
2021-05-04 08:20:35 -04:00
|
|
|
local inputPrefix = pipePrefix..'_input_'
|
|
|
|
local outputPrefix = pipePrefix..'_output_'
|
2021-05-01 19:39:35 -04:00
|
|
|
|
2021-04-09 15:06:55 -04:00
|
|
|
local function message(_M, msg, color)
|
|
|
|
if color == nil then
|
|
|
|
color = 0x00009900
|
|
|
|
end
|
|
|
|
|
|
|
|
for i=#_M.onMessageHandler,1,-1 do
|
|
|
|
_M.onMessageHandler[i](msg, color)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-09 17:47:08 -04:00
|
|
|
local function save(_M, filename)
|
2021-04-09 15:06:55 -04:00
|
|
|
for i=#_M.onSaveHandler,1,-1 do
|
2021-04-09 17:47:08 -04:00
|
|
|
_M.onSaveHandler[i](filename)
|
2021-04-09 15:06:55 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function onSave(_M, handler)
|
|
|
|
table.insert(_M.onSaveHandler, handler)
|
|
|
|
end
|
|
|
|
|
2021-04-09 17:47:08 -04:00
|
|
|
local function load(_M, filename)
|
2021-04-09 15:06:55 -04:00
|
|
|
for i=#_M.onLoadHandler,1,-1 do
|
2021-04-09 17:47:08 -04:00
|
|
|
_M.onLoadHandler[i](filename)
|
2021-04-09 15:06:55 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function onLoad(_M, handler)
|
|
|
|
table.insert(_M.onLoadHandler, handler)
|
|
|
|
end
|
|
|
|
|
2021-05-05 08:38:43 -04:00
|
|
|
local function reset(_M)
|
|
|
|
for i=#_M.onResetHandler,1,-1 do
|
|
|
|
_M.onResetHandler[i]()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function onReset(_M, handler)
|
|
|
|
table.insert(_M.onResetHandler, handler)
|
|
|
|
end
|
|
|
|
|
2021-04-09 17:47:08 -04:00
|
|
|
local function onMessage(_M, handler)
|
|
|
|
table.insert(_M.onMessageHandler, handler)
|
|
|
|
end
|
|
|
|
|
2021-05-01 19:39:35 -04:00
|
|
|
--- Launches the child processes
|
|
|
|
---@param _M table The instance
|
|
|
|
---@param count integer Number of processes needed
|
|
|
|
---@return Promise Promise A promise that resolves when all the processes are ready
|
|
|
|
local function launchChildren(_M, count)
|
2021-05-04 08:20:35 -04:00
|
|
|
local promises = {}
|
|
|
|
for i=#_M.poppets+1,count,1 do
|
|
|
|
local newOne = {
|
|
|
|
process = nil,
|
|
|
|
output = util.openReadPipe(outputPrefix..i),
|
|
|
|
input = nil,
|
|
|
|
}
|
|
|
|
|
2021-05-04 20:30:52 -04:00
|
|
|
local outputPipeName = outputPrefix..i
|
2021-05-04 08:20:35 -04:00
|
|
|
local inputPipeName = inputPrefix..i
|
2021-05-01 19:39:35 -04:00
|
|
|
|
|
|
|
local settingsDir = nil
|
|
|
|
if util.isWin then
|
2021-05-04 20:30:52 -04:00
|
|
|
settingsDir = util.getTempDir().."/donk_runner_settings_"..i
|
2021-05-01 19:39:35 -04:00
|
|
|
util.mkdir(settingsDir)
|
|
|
|
end
|
|
|
|
|
|
|
|
local envs = {
|
2021-05-04 08:20:35 -04:00
|
|
|
RUNNER_INPUT_PIPE = inputPipeName,
|
2021-05-04 20:30:52 -04:00
|
|
|
RUNNER_OUTPUT_PIPE = outputPipeName,
|
2021-05-01 19:39:35 -04:00
|
|
|
APPDATA = settingsDir,
|
|
|
|
}
|
|
|
|
|
|
|
|
local cmd = '"'.._M.hostProcess..'" "--rom='..config.ROM..'" --unpause "--lua='..base..'/runner-process.lua"'
|
2021-05-04 08:20:35 -04:00
|
|
|
newOne.process = util.popenCmd(cmd, nil, envs)
|
2021-05-01 19:39:35 -04:00
|
|
|
|
2021-05-04 08:20:35 -04:00
|
|
|
-- Wait for init
|
|
|
|
local promise = util.promiseWrap(function()
|
|
|
|
newOne.output:read("*l")
|
|
|
|
while newOne.input == nil do
|
2021-05-04 20:30:52 -04:00
|
|
|
newOne.input = util.openReadPipeWriter(inputPipeName)
|
2021-05-04 08:20:35 -04:00
|
|
|
end
|
|
|
|
end)
|
|
|
|
table.insert(promises, promise)
|
|
|
|
table.insert(_M.poppets, newOne)
|
2021-05-01 19:39:35 -04:00
|
|
|
end
|
|
|
|
|
2021-05-04 08:20:35 -04:00
|
|
|
return Promise.all(table.unpack(promises))
|
2021-05-01 19:39:35 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
return function(promise)
|
|
|
|
-- FIXME Should this be a global???
|
|
|
|
Promise = promise
|
2021-05-03 03:23:04 -04:00
|
|
|
if util == nil then
|
|
|
|
util = dofile(base.."/util.lua")(Promise)
|
|
|
|
end
|
2021-05-01 19:39:35 -04:00
|
|
|
-- FIXME Maybe don't do this in the "constructor"?
|
2021-04-30 00:54:35 -04:00
|
|
|
if util.isWin then
|
2021-05-04 08:20:35 -04:00
|
|
|
util.downloadFile("https://github.com/psmay/windows-named-pipe-utils/releases/download/v0.1.1/build.zip", base.."/namedpipe.zip")
|
|
|
|
util.unzip(base.."/namedpipe.zip", base)
|
|
|
|
os.rename(base.."/build", "namedpipe")
|
2021-04-30 00:54:35 -04:00
|
|
|
end
|
2021-04-29 07:22:08 -04:00
|
|
|
|
2021-04-09 15:06:55 -04:00
|
|
|
local _M = {
|
|
|
|
onMessageHandler = {},
|
2021-05-05 08:38:43 -04:00
|
|
|
onResetHandler = {},
|
2021-04-09 17:47:08 -04:00
|
|
|
onSaveHandler = {},
|
|
|
|
onLoadHandler = {},
|
2021-04-28 18:54:39 -04:00
|
|
|
poppets = {},
|
2021-04-29 08:51:03 -04:00
|
|
|
hostProcess = "lsnes",
|
2021-04-09 15:06:55 -04:00
|
|
|
}
|
|
|
|
|
2021-04-29 08:51:03 -04:00
|
|
|
if util.isWin then
|
|
|
|
_M.hostProcess = util.scrapeCmd('*l', 'powershell "(Get-WmiObject Win32_Process -Filter ProcessId=$((Get-WmiObject Win32_Process -Filter ProcessId=$((Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId)).ParentProcessId)").ExecutablePath')
|
|
|
|
if _M.hostProcess == nil or _M.hostProcess == "" then
|
|
|
|
_M.hostProcess = "lsnes-bsnes.exe"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- FIXME Linux
|
|
|
|
end
|
|
|
|
|
2021-04-09 15:06:55 -04:00
|
|
|
_M.onRenderForm = function(handler)
|
|
|
|
end
|
|
|
|
|
|
|
|
_M.onMessage = function(handler)
|
|
|
|
onMessage(_M, handler)
|
|
|
|
end
|
|
|
|
|
2021-04-09 17:47:08 -04:00
|
|
|
_M.message = function(msg, color)
|
|
|
|
message(_M, msg, color)
|
|
|
|
end
|
|
|
|
|
|
|
|
_M.onSave = function(handler)
|
|
|
|
onSave(_M, handler)
|
|
|
|
end
|
|
|
|
|
|
|
|
_M.onLoad = function(handler)
|
|
|
|
onLoad(_M, handler)
|
|
|
|
end
|
|
|
|
|
2021-05-05 08:38:43 -04:00
|
|
|
_M.onReset = function(handler)
|
|
|
|
onReset(_M, handler)
|
|
|
|
end
|
|
|
|
|
2021-05-01 19:39:35 -04:00
|
|
|
_M.run = function(speciesSlice, generationIdx, genomeCallback)
|
|
|
|
local promise = Promise.new()
|
|
|
|
promise:resolve()
|
|
|
|
return promise:next(function()
|
|
|
|
return launchChildren(_M, #speciesSlice)
|
|
|
|
end):next(function()
|
|
|
|
message(_M, 'Setting up child processes')
|
2021-04-28 18:54:39 -04:00
|
|
|
|
2021-05-01 19:39:35 -04:00
|
|
|
for i=1,#speciesSlice,1 do
|
2021-05-04 08:20:35 -04:00
|
|
|
local inputPipe = _M.poppets[i].input
|
|
|
|
inputPipe:write(serpent.dump({speciesSlice[i], generationIdx}).."\n")
|
|
|
|
inputPipe:flush()
|
2021-05-01 19:39:35 -04:00
|
|
|
end
|
2021-04-28 18:54:39 -04:00
|
|
|
|
2021-05-01 19:39:35 -04:00
|
|
|
message(_M, 'Waiting for child processes to finish')
|
|
|
|
|
2021-05-08 00:07:52 -04:00
|
|
|
local function readLoop(outputPipe)
|
2021-05-04 08:20:35 -04:00
|
|
|
return util.promiseWrap(function()
|
2021-05-08 00:07:52 -04:00
|
|
|
return outputPipe:read("*l")
|
|
|
|
end):next(function(line)
|
2021-05-04 08:20:35 -04:00
|
|
|
if line == nil or line == "" then
|
|
|
|
util.closeCmd(outputPipe)
|
|
|
|
end
|
2021-05-01 19:39:35 -04:00
|
|
|
|
|
|
|
local ok, obj = serpent.load(line)
|
|
|
|
if not ok then
|
2021-05-04 08:20:35 -04:00
|
|
|
return false
|
2021-05-01 19:39:35 -04:00
|
|
|
end
|
2021-04-28 23:18:26 -04:00
|
|
|
|
2021-05-01 19:39:35 -04:00
|
|
|
if obj == nil then
|
2021-05-04 08:20:35 -04:00
|
|
|
return false
|
2021-05-01 19:39:35 -04:00
|
|
|
end
|
2021-04-28 18:54:39 -04:00
|
|
|
|
2021-05-01 19:39:35 -04:00
|
|
|
if obj.type == 'onMessage' then
|
|
|
|
message(_M, obj.msg, obj.color)
|
|
|
|
elseif obj.type == 'onLoad' then
|
|
|
|
load(_M, obj.filename)
|
|
|
|
elseif obj.type == 'onSave' then
|
|
|
|
save(_M, obj.filename)
|
2021-05-05 08:38:43 -04:00
|
|
|
elseif obj.type == 'onReset' then
|
|
|
|
reset(_M)
|
2021-05-01 19:39:35 -04:00
|
|
|
elseif obj.type == 'onGenome' then
|
|
|
|
for i=1,#speciesSlice,1 do
|
|
|
|
local s = speciesSlice[i]
|
|
|
|
if s.id == obj.speciesId then
|
2021-05-07 23:43:15 -04:00
|
|
|
message(_M, string.format('Write Species %d Genome %d', obj.speciesId, obj.genomeIndex))
|
2021-05-01 19:39:35 -04:00
|
|
|
s.genomes[obj.genomeIndex] = obj.genome
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
genomeCallback(obj.genome, obj.index)
|
|
|
|
elseif obj.type == 'onFinish' then
|
2021-05-04 08:20:35 -04:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
end):next(function(finished)
|
|
|
|
if finished then
|
|
|
|
return
|
2021-04-24 01:18:06 -04:00
|
|
|
end
|
2021-05-01 19:39:35 -04:00
|
|
|
|
2021-05-08 00:07:52 -04:00
|
|
|
return readLoop(outputPipe)
|
2021-05-04 08:20:35 -04:00
|
|
|
end)
|
2021-05-01 19:39:35 -04:00
|
|
|
end
|
2021-05-04 08:20:35 -04:00
|
|
|
|
|
|
|
local waiters = {}
|
|
|
|
for i=1,#speciesSlice,1 do
|
2021-05-07 23:35:10 -04:00
|
|
|
local outputPipe = _M.poppets[i].output
|
2021-05-08 00:07:52 -04:00
|
|
|
local waiter = readLoop(outputPipe)
|
2021-05-04 08:20:35 -04:00
|
|
|
table.insert(waiters, waiter)
|
|
|
|
end
|
|
|
|
|
|
|
|
return Promise.all(table.unpack(waiters))
|
|
|
|
end):next(function()
|
|
|
|
message(_M, 'Child processes finished')
|
2021-05-01 19:39:35 -04:00
|
|
|
end)
|
2021-04-09 15:06:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
return _M
|
|
|
|
end
|