Promisify everything?
This commit is contained in:
parent
088c92113b
commit
c7819c98f2
10 changed files with 487 additions and 414 deletions
2
game.lua
2
game.lua
|
@ -6,7 +6,7 @@ local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
|||
local mathFunctions = dofile(base.."/mathFunctions.lua")
|
||||
local config = dofile(base.."/config.lua")
|
||||
local spritelist = dofile(base.."/spritelist.lua")
|
||||
local util = dofile(base.."/util.lua")
|
||||
local util = dofile(base.."/util.lua")()
|
||||
local mem = dofile(base.."/mem.lua")
|
||||
local _M = {
|
||||
leader = 0,
|
||||
|
|
|
@ -4,7 +4,6 @@ local gui = gui
|
|||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
local pool = dofile(base.."/pool.lua")
|
||||
local util = dofile(base.."/util.lua")
|
||||
|
||||
local statusLine = nil
|
||||
local statusColor = 0x0000ff00
|
||||
|
@ -30,4 +29,10 @@ pool.onRenderForm(function(form)
|
|||
gui.text(-500, guiHeight - 20, statusLine, 0x00000000)
|
||||
end
|
||||
end)
|
||||
pool.run()
|
||||
|
||||
pool.run():next(function()
|
||||
print("The pool finished running!!!")
|
||||
end):catch(function(error)
|
||||
io.stderr:write(string.format("There was a problem running the pool: %s", error))
|
||||
print(string.format("There was a problem running the pool: %s", error))
|
||||
end)
|
114
pool.lua
114
pool.lua
|
@ -1,7 +1,17 @@
|
|||
local callback, set_timer_timeout = callback, set_timer_timeout
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
local Promise = dofile(base.."/promise.lua")
|
||||
-- Only the parent should manage ticks!
|
||||
callback.register('timer', function()
|
||||
Promise.update()
|
||||
set_timer_timeout(1)
|
||||
end)
|
||||
set_timer_timeout(1)
|
||||
|
||||
local config = dofile(base.."/config.lua")
|
||||
local util = dofile(base.."/util.lua")
|
||||
local util = dofile(base.."/util.lua")(Promise)
|
||||
local serpent = dofile(base.."/serpent.lua")
|
||||
local libDeflate = dofile(base.."/LibDeflate.lua")
|
||||
|
||||
|
@ -386,15 +396,17 @@ local function addToSpecies(child)
|
|||
end
|
||||
end
|
||||
|
||||
local function initializePool(after)
|
||||
local function initializePool()
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
pool = newPool()
|
||||
|
||||
for i=1,config.NeatConfig.Population do
|
||||
basic = basicGenome()
|
||||
local basic = basicGenome()
|
||||
addToSpecies(basic)
|
||||
end
|
||||
|
||||
after()
|
||||
end)
|
||||
end
|
||||
|
||||
local function bytes(x)
|
||||
|
@ -406,6 +418,9 @@ local function bytes(x)
|
|||
end
|
||||
|
||||
local function writeFile(filename)
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function ()
|
||||
local file = io.open(filename, "w")
|
||||
local dump = serpent.dump(pool)
|
||||
local zlib = libDeflate:CompressDeflate(dump)
|
||||
|
@ -414,11 +429,14 @@ local function writeFile(filename)
|
|||
file:write(string.char(0,0,0,0))
|
||||
file:write(bytes(#dump % (2^32)))
|
||||
file:close()
|
||||
return
|
||||
end)
|
||||
end
|
||||
|
||||
-- FIXME Save/load mechanism has to be rethought with items running in parallel
|
||||
local function loadFile(filename, after)
|
||||
-- FIXME This isn't technically asynchronous. Probably can't be though.
|
||||
local function loadFile(filename)
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
message("Loading pool from " .. filename, 0x00999900)
|
||||
local file = io.open(filename, "r")
|
||||
if file == nil then
|
||||
|
@ -433,6 +451,7 @@ local function loadFile(filename, after)
|
|||
end
|
||||
|
||||
pool = obj
|
||||
end)
|
||||
end
|
||||
|
||||
local function savePool()
|
||||
|
@ -441,9 +460,8 @@ local function savePool()
|
|||
message(string.format("Saved \"%s\"!", filename:sub(#filename - 50)), 0x00009900)
|
||||
end
|
||||
|
||||
local function loadPool(after)
|
||||
loadFile(_M.saveLoadFile, after)
|
||||
after()
|
||||
local function loadPool()
|
||||
return loadFile(_M.saveLoadFile)
|
||||
end
|
||||
|
||||
local function processRenderForm(form)
|
||||
|
@ -471,7 +489,7 @@ end
|
|||
local function crossover(g1, g2)
|
||||
-- Make sure g1 is the higher fitness genome
|
||||
if g2.fitness > g1.fitness then
|
||||
tempg = g1
|
||||
local tempg = g1
|
||||
g1 = g2
|
||||
g2 = tempg
|
||||
end
|
||||
|
@ -562,11 +580,11 @@ end
|
|||
local function breedChild(species)
|
||||
local child = {}
|
||||
if math.random() < config.NeatConfig.CrossoverChance then
|
||||
g1 = species.genomes[math.random(1, #species.genomes)]
|
||||
g2 = species.genomes[math.random(1, #species.genomes)]
|
||||
local g1 = species.genomes[math.random(1, #species.genomes)]
|
||||
local g2 = species.genomes[math.random(1, #species.genomes)]
|
||||
child = crossover(g1, g2)
|
||||
else
|
||||
g = species.genomes[math.random(1, #species.genomes)]
|
||||
local g = species.genomes[math.random(1, #species.genomes)]
|
||||
child = copyGenome(g)
|
||||
end
|
||||
|
||||
|
@ -605,7 +623,7 @@ local function removeWeakSpecies()
|
|||
local sum = totalAverageFitness()
|
||||
for s = 1,#pool.species do
|
||||
local species = pool.species[s]
|
||||
breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population)
|
||||
local breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population)
|
||||
if breed >= 1 then
|
||||
table.insert(survived, species)
|
||||
end
|
||||
|
@ -628,7 +646,7 @@ local function newGeneration()
|
|||
local children = {}
|
||||
for s = 1,#pool.species do
|
||||
local species = pool.species[s]
|
||||
breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population) - 1
|
||||
local breed = math.floor(species.averageFitness / sum * config.NeatConfig.Population) - 1
|
||||
for i=1,breed do
|
||||
table.insert(children, breedChild(species))
|
||||
end
|
||||
|
@ -652,7 +670,7 @@ local function newGeneration()
|
|||
writeFile(_M.saveLoadFile .. ".gen" .. pool.generation .. ".pool")
|
||||
end
|
||||
|
||||
local runner = Runner()
|
||||
local runner = Runner(Promise)
|
||||
runner.onMessage(function(msg, color)
|
||||
message(msg, color)
|
||||
end)
|
||||
|
@ -671,11 +689,20 @@ local topRequested = false
|
|||
|
||||
local loadRequested = false
|
||||
local saveRequested = false
|
||||
local function mainLoop(currentSpecies)
|
||||
local function mainLoop(currentSpecies, topGenome)
|
||||
if currentSpecies == nil then
|
||||
currentSpecies = 1
|
||||
end
|
||||
|
||||
local slice = pool.species[currentSpecies]
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
if loadRequested then
|
||||
loadRequested = false
|
||||
loadPool(mainLoop)
|
||||
return
|
||||
currentSpecies = nil
|
||||
-- FIXME
|
||||
return loadPool()
|
||||
end
|
||||
|
||||
if saveRequested then
|
||||
|
@ -685,19 +712,13 @@ local function mainLoop(currentSpecies)
|
|||
|
||||
if topRequested then
|
||||
topRequested = false
|
||||
playTop()
|
||||
return
|
||||
return playTop()
|
||||
end
|
||||
|
||||
if not config.Running then
|
||||
-- FIXME Tick?
|
||||
end
|
||||
|
||||
if currentSpecies == nil then
|
||||
currentSpecies = 1
|
||||
end
|
||||
|
||||
local slice = pool.species[currentSpecies]
|
||||
if hasThreads then
|
||||
slice = {}
|
||||
for i=currentSpecies, currentSpecies + config.NeatConfig.Threads - 1, 1 do
|
||||
|
@ -709,18 +730,16 @@ local function mainLoop(currentSpecies)
|
|||
end
|
||||
end
|
||||
local finished = 0
|
||||
runner.run(
|
||||
|
||||
return runner.run(
|
||||
slice,
|
||||
pool.generation,
|
||||
function()
|
||||
-- Genome callback
|
||||
end,
|
||||
function()
|
||||
if hasThreads then
|
||||
finished = finished + 1
|
||||
if finished ~= #slice then
|
||||
return
|
||||
-- FIXME Should we do something here??? What was your plan, past me?
|
||||
end
|
||||
):next(function()
|
||||
if hasThreads then
|
||||
currentSpecies = currentSpecies + #slice
|
||||
else
|
||||
currentSpecies = currentSpecies + 1
|
||||
|
@ -730,9 +749,12 @@ local function mainLoop(currentSpecies)
|
|||
newGeneration()
|
||||
currentSpecies = 1
|
||||
end
|
||||
mainLoop(currentSpecies)
|
||||
end)
|
||||
end):next(function ()
|
||||
if topGenome == nil then
|
||||
return mainLoop(currentSpecies)
|
||||
end
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
playTop = function()
|
||||
|
@ -749,7 +771,7 @@ playTop = function()
|
|||
end
|
||||
|
||||
-- FIXME genome
|
||||
mainLoop(maxs)
|
||||
return mainLoop(maxs, maxg)
|
||||
end
|
||||
|
||||
function _M.requestLoad(filename)
|
||||
|
@ -775,15 +797,19 @@ function _M.requestTop()
|
|||
end
|
||||
|
||||
function _M.run(reset)
|
||||
local promise = nil
|
||||
if pool == nil or reset == true then
|
||||
initializePool(function()
|
||||
writeFile(config.PoolDir.."temp.pool")
|
||||
mainLoop()
|
||||
end)
|
||||
promise = initializePool()
|
||||
else
|
||||
writeFile(config.PoolDir.."temp.pool")
|
||||
mainLoop()
|
||||
promise = Promise.new()
|
||||
promise:resolve()
|
||||
end
|
||||
|
||||
return promise:next(function()
|
||||
return writeFile(config.PoolDir.."temp.pool")
|
||||
end):next(function ()
|
||||
return mainLoop()
|
||||
end)
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
|
@ -78,7 +78,6 @@ transition = function(promise, state, value)
|
|||
if promise.state == state
|
||||
or promise.state ~= State.PENDING
|
||||
or ( state ~= State.FULFILLED and state ~= State.REJECTED )
|
||||
or value == nil
|
||||
then
|
||||
return
|
||||
end
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
local gui, utime = gui, utime
|
||||
local gui, utime, callback, set_timer_timeout = gui, utime, callback, set_timer_timeout
|
||||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
local Promise = dofile(base.."/promise.lua")
|
||||
-- Only the parent should manage ticks!
|
||||
callback.register('timer', function()
|
||||
Promise.update()
|
||||
set_timer_timeout(1)
|
||||
end)
|
||||
set_timer_timeout(1)
|
||||
|
||||
local Runner = dofile(base.."/runner.lua")
|
||||
local serpent = dofile(base.."/serpent.lua")
|
||||
local util = dofile(base.."/util.lua")
|
||||
local util = dofile(base.."/util.lua")(Promise)
|
||||
|
||||
local inputFilePath = os.getenv("RUNNER_INPUT_FILE")
|
||||
local outputFilePath = os.getenv("RUNNER_OUTPUT_FILE")
|
||||
|
||||
local first = false
|
||||
|
||||
local outContents = {}
|
||||
|
||||
local statusLine = nil
|
||||
|
@ -22,7 +26,7 @@ local species = nil
|
|||
local speciesId = -1
|
||||
local generationIndex = nil
|
||||
|
||||
local runner = Runner()
|
||||
local runner = Runner(Promise)
|
||||
runner.onMessage(function(msg, color)
|
||||
statusLine = msg
|
||||
statusColor = color
|
||||
|
@ -78,32 +82,7 @@ runner.onLoad(function(filename)
|
|||
)
|
||||
end)
|
||||
|
||||
local waiter = nil
|
||||
if not util.isWin then
|
||||
waiter = util.startWaiting(inputFilePath)
|
||||
end
|
||||
|
||||
local function waitLoop()
|
||||
if not first then
|
||||
local sec, usec = utime()
|
||||
local ts = sec * 1000000 + usec
|
||||
|
||||
local outFile = io.open(outputFilePath, "w")
|
||||
outFile:write(serpent.dump({ type = 'onInit', ts = ts }))
|
||||
outFile:close()
|
||||
|
||||
print(string.format('Wrote init to output at %d', ts))
|
||||
|
||||
|
||||
first = true
|
||||
end
|
||||
|
||||
print('Waiting for input from master process')
|
||||
|
||||
if not util.isWin then
|
||||
util.finishWaiting(waiter)
|
||||
end
|
||||
|
||||
local inputData = nil
|
||||
local ok = false
|
||||
while not ok or inputData == nil or speciesId == inputData[1].id do
|
||||
|
@ -128,7 +107,7 @@ local function waitLoop()
|
|||
|
||||
print('Running')
|
||||
|
||||
runner.run(
|
||||
return runner.run(
|
||||
species,
|
||||
generationIndex,
|
||||
function(genome, index)
|
||||
|
@ -141,8 +120,8 @@ local function waitLoop()
|
|||
speciesId = speciesId,
|
||||
})
|
||||
)
|
||||
end,
|
||||
function()
|
||||
end
|
||||
):next(function()
|
||||
table.insert(
|
||||
outContents,
|
||||
serpent.dump({
|
||||
|
@ -156,8 +135,12 @@ local function waitLoop()
|
|||
local inputFile = io.open(inputFilePath, "w")
|
||||
inputFile:close()
|
||||
|
||||
if not util.isWin then
|
||||
waiter = util.startWaiting(inputFilePath)
|
||||
local waiter = nil
|
||||
if util.isWin then
|
||||
waiter = Promise.new()
|
||||
waiter:resolve()
|
||||
else
|
||||
waiter = util.waitForFiles(inputFilePath)
|
||||
end
|
||||
|
||||
-- Write the result
|
||||
|
@ -165,9 +148,27 @@ local function waitLoop()
|
|||
outFile:write(table.concat(outContents, "\n"))
|
||||
outFile:close()
|
||||
|
||||
waitLoop()
|
||||
end
|
||||
)
|
||||
return waiter
|
||||
end):next(waitLoop)
|
||||
end
|
||||
|
||||
waitLoop()
|
||||
local waiter = nil
|
||||
if util.isWin then
|
||||
waiter = Promise.new()
|
||||
waiter:resolve()
|
||||
else
|
||||
waiter = util.waitForFiles(inputFilePath)
|
||||
end
|
||||
|
||||
local sec, usec = utime()
|
||||
local ts = sec * 1000000 + usec
|
||||
|
||||
local outFile = io.open(outputFilePath, "w")
|
||||
outFile:write(serpent.dump({ type = 'onInit', ts = ts }))
|
||||
outFile:close()
|
||||
|
||||
print(string.format('Wrote init to output at %d', ts))
|
||||
|
||||
waiter:next(waitLoop):catch(function(error)
|
||||
print('ERROR: '..error)
|
||||
end)
|
|
@ -2,7 +2,9 @@ local random = random
|
|||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
local util = dofile(base.."/util.lua")
|
||||
local Promise = nil
|
||||
|
||||
local util = nil
|
||||
local config = dofile(base.."/config.lua")
|
||||
local serpent = dofile(base.."/serpent.lua")
|
||||
local temps = {
|
||||
|
@ -25,6 +27,9 @@ local tmpFileName = tempDir.."/donk_runner_"..
|
|||
string.hex(math.floor(random.integer(0, 0xffffffff)))..
|
||||
string.hex(math.floor(random.integer(0, 0xffffffff)))
|
||||
|
||||
local inputPrefix = tmpFileName..'_input_'
|
||||
local outputPrefix = tmpFileName..'_output_'
|
||||
|
||||
local function message(_M, msg, color)
|
||||
if color == nil then
|
||||
color = 0x00009900
|
||||
|
@ -59,7 +64,46 @@ local function onMessage(_M, handler)
|
|||
table.insert(_M.onMessageHandler, handler)
|
||||
end
|
||||
|
||||
return function()
|
||||
--- 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)
|
||||
local children = {}
|
||||
while #_M.poppets < count do
|
||||
local i = #_M.poppets+1
|
||||
local outputFileName = outputPrefix..i
|
||||
local inputFileName = inputPrefix..i
|
||||
|
||||
local settingsDir = nil
|
||||
if util.isWin then
|
||||
settingsDir = tempDir.."/donk_runner_settings_"..i
|
||||
util.mkdir(settingsDir)
|
||||
end
|
||||
|
||||
local envs = {
|
||||
RUNNER_INPUT_FILE = inputFileName,
|
||||
RUNNER_OUTPUT_FILE = outputFileName,
|
||||
APPDATA = settingsDir,
|
||||
}
|
||||
|
||||
local child = util.waitForFiles(outputFileName)
|
||||
|
||||
local cmd = '"'.._M.hostProcess..'" "--rom='..config.ROM..'" --unpause "--lua='..base..'/runner-process.lua"'
|
||||
local poppet = util.popenCmd(cmd, nil, envs)
|
||||
table.insert(_M.poppets, poppet)
|
||||
|
||||
table.insert(children, child)
|
||||
end
|
||||
|
||||
return Promise.all(table.unpack(children))
|
||||
end
|
||||
|
||||
return function(promise)
|
||||
-- FIXME Should this be a global???
|
||||
Promise = promise
|
||||
util = dofile(base.."/util.lua")(Promise)
|
||||
-- FIXME Maybe don't do this in the "constructor"?
|
||||
if util.isWin then
|
||||
util.downloadFile('https://github.com/watchexec/watchexec/releases/download/1.13.1/watchexec-1.13.1-x86_64-pc-windows-gnu.zip', base..'/watchexec.zip')
|
||||
util.unzip(base..'/watchexec.zip', base)
|
||||
|
@ -102,12 +146,12 @@ return function()
|
|||
onLoad(_M, handler)
|
||||
end
|
||||
|
||||
_M.run = function(species, generationIdx, genomeCallback, finishCallback)
|
||||
local inputPrefix = tmpFileName..'_input_'
|
||||
local outputPrefix = tmpFileName..'_output_'
|
||||
|
||||
_M.run = function(speciesSlice, generationIdx, genomeCallback)
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
return promise:next(function()
|
||||
-- Create the input files and output files
|
||||
for i=1,#species,1 do
|
||||
for i=1,#speciesSlice,1 do
|
||||
local inputFileName = inputPrefix..i
|
||||
local inputFile = io.open(inputFileName, 'a')
|
||||
inputFile:close()
|
||||
|
@ -117,58 +161,33 @@ return function()
|
|||
outputFile:close()
|
||||
end
|
||||
|
||||
while #_M.poppets < #species do
|
||||
local i = #_M.poppets+1
|
||||
local outputFileName = outputPrefix..i
|
||||
local inputFileName = inputPrefix..i
|
||||
|
||||
message(_M, _M.hostProcess)
|
||||
|
||||
local settingsDir = nil
|
||||
if isWin then
|
||||
settingsDir = tempDir.."/donk_runner_settings_"..i
|
||||
util.mkdir(settingsDir)
|
||||
return launchChildren(_M, #speciesSlice)
|
||||
end):next(function()
|
||||
local outputFileNames = {}
|
||||
for i=1,#speciesSlice,1 do
|
||||
table.insert(outputFileNames, outputPrefix..i)
|
||||
end
|
||||
|
||||
local envs = {
|
||||
RUNNER_INPUT_FILE = inputFileName,
|
||||
RUNNER_OUTPUT_FILE = outputFileName,
|
||||
APPDATA = settingsDir,
|
||||
}
|
||||
|
||||
local waiter = util.startWaiting(outputFileName)
|
||||
|
||||
local cmd = '"'.._M.hostProcess..'" "--rom='..config.ROM..'" --unpause "--lua='..base..'/runner-process.lua"'
|
||||
local poppet = util.popenCmd(cmd, nil, envs)
|
||||
table.insert(_M.poppets, poppet)
|
||||
|
||||
util.finishWaiting(waiter)
|
||||
end
|
||||
|
||||
local waiters = {}
|
||||
for i=1,#species,1 do
|
||||
table.insert(waiters, outputPrefix..i)
|
||||
end
|
||||
|
||||
local waiter = util.startWaiting(waiters, nil, tmpFileName.."_output_*")
|
||||
local waiter = util.waitForFiles(outputFileNames, nil, tmpFileName.."_output_*")
|
||||
|
||||
message(_M, 'Setting up child processes')
|
||||
|
||||
for i=1,#species,1 do
|
||||
for i=1,#speciesSlice,1 do
|
||||
|
||||
local inputFileName = tmpFileName.."_input_"..i
|
||||
local inputFile = io.open(inputFileName, 'w')
|
||||
inputFile:write(serpent.dump({species[i], generationIdx}))
|
||||
inputFile:write(serpent.dump({speciesSlice[i], generationIdx}))
|
||||
inputFile:close()
|
||||
end
|
||||
|
||||
message(_M, 'Waiting for child processes to finish')
|
||||
|
||||
util.finishWaiting(waiter, #waiters)
|
||||
|
||||
return waiter
|
||||
end):next(function()
|
||||
message(_M, 'Child processes finished')
|
||||
|
||||
for i=1,#species,1 do
|
||||
local finished = 0
|
||||
for i=1,#speciesSlice,1 do
|
||||
message(_M, "Processing output "..i)
|
||||
local outputFileName = tmpFileName..'_output_'..i
|
||||
local outputFile = io.open(outputFileName, "r")
|
||||
|
@ -190,8 +209,8 @@ return function()
|
|||
elseif obj.type == 'onSave' then
|
||||
save(_M, obj.filename)
|
||||
elseif obj.type == 'onGenome' then
|
||||
for i=1,#species,1 do
|
||||
local s = species[i]
|
||||
for i=1,#speciesSlice,1 do
|
||||
local s = speciesSlice[i]
|
||||
if s.id == obj.speciesId then
|
||||
s.genomes[obj.genomeIndex] = obj.genome
|
||||
break
|
||||
|
@ -199,14 +218,19 @@ return function()
|
|||
end
|
||||
genomeCallback(obj.genome, obj.index)
|
||||
elseif obj.type == 'onFinish' then
|
||||
finishCallback()
|
||||
finished = finished + 1
|
||||
if finished == #speciesSlice then
|
||||
outputFile:close()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
::continue::
|
||||
line = outputFile:read()
|
||||
until(line == "" or line == nil)
|
||||
outputFile:close()
|
||||
end
|
||||
error(string.format("Some processes never finished? Saw %d terminations.", finished))
|
||||
end)
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
253
runner.lua
253
runner.lua
|
@ -12,6 +12,8 @@ local Outputs = #config.ButtonNames
|
|||
local guiWidth = 0
|
||||
local guiHeight = 0
|
||||
|
||||
local Promise = nil
|
||||
|
||||
local function message(_M, msg, color)
|
||||
if color == nil then
|
||||
color = 0x00009900
|
||||
|
@ -213,19 +215,21 @@ local function displayGenome(genome)
|
|||
gui.renderctx.setnull()
|
||||
end
|
||||
|
||||
local function advanceFrame(_M, after)
|
||||
table.insert(_M.onFrameAdvancedHandler, after)
|
||||
local function advanceFrame(_M)
|
||||
local promise = Promise.new()
|
||||
table.insert(_M.onFrameAdvancedHandler, promise)
|
||||
return promise
|
||||
end
|
||||
|
||||
local function processFrameAdvanced(_M)
|
||||
for i=#_M.onFrameAdvancedHandler,1,-1 do
|
||||
table.remove(_M.onFrameAdvancedHandler, i)()
|
||||
table.remove(_M.onFrameAdvancedHandler, i):resolve()
|
||||
end
|
||||
end
|
||||
|
||||
local buttons = nil
|
||||
local buttonCtx = gui.renderctx.new(500, 70)
|
||||
function displayButtons()
|
||||
local function displayButtons(_M)
|
||||
buttonCtx:set()
|
||||
buttonCtx:clear()
|
||||
|
||||
|
@ -249,7 +253,7 @@ function displayButtons()
|
|||
|
||||
local insert = ""
|
||||
local confirm = "[Tab] Type in filename"
|
||||
if inputmode then
|
||||
if _M.inputmode then
|
||||
insert = "_"
|
||||
confirm = "[Tab] Confirm filename"
|
||||
end
|
||||
|
@ -264,7 +268,7 @@ end
|
|||
|
||||
local formCtx = nil
|
||||
local form = nil
|
||||
function displayForm(_M)
|
||||
local function displayForm(_M)
|
||||
if #_M.onRenderFormHandler == 0 then
|
||||
return
|
||||
end
|
||||
|
@ -303,7 +307,7 @@ function displayForm(_M)
|
|||
gui.text(320, 65, string.format("Current Area: %04x", _M.currentArea))
|
||||
gui.text(320, 80, "Rightmost: "..rightmost)
|
||||
|
||||
displayButtons()
|
||||
displayButtons(_M)
|
||||
formCtx:set()
|
||||
buttons:draw(5, 130)
|
||||
|
||||
|
@ -373,11 +377,12 @@ local function evaluateNetwork(_M, network, inputs, inputDeltas)
|
|||
return outputs
|
||||
end
|
||||
|
||||
local controller = nil
|
||||
local function evaluateCurrent(_M)
|
||||
local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
|
||||
|
||||
local inputDeltas = {}
|
||||
inputs, inputDeltas = game.getInputs()
|
||||
local inputs, inputDeltas = game.getInputs()
|
||||
|
||||
controller = evaluateNetwork(_M, genome.network, inputs, inputDeltas)
|
||||
|
||||
|
@ -405,9 +410,70 @@ local function fitnessAlreadyMeasured(_M)
|
|||
return genome.fitness ~= 0
|
||||
end
|
||||
|
||||
local rewinds = {}
|
||||
local rew = movie.to_rewind(config.NeatConfig.Filename)
|
||||
local function rewind()
|
||||
local promise = Promise.new()
|
||||
movie.unsafe_rewind(rew)
|
||||
table.insert(rewinds, promise)
|
||||
return promise
|
||||
end
|
||||
|
||||
local function initializeRun(_M, after)
|
||||
local function rewound()
|
||||
for i=#rewinds,1,-1 do
|
||||
local promise = table.remove(rewinds, i)
|
||||
promise:resolve()
|
||||
end
|
||||
end
|
||||
|
||||
local function newNeuron()
|
||||
local neuron = {}
|
||||
neuron.incoming = {}
|
||||
neuron.value = 0.0
|
||||
--neuron.dw = 1
|
||||
return neuron
|
||||
end
|
||||
|
||||
local function generateNetwork(genome)
|
||||
local network = {}
|
||||
network.neurons = {}
|
||||
|
||||
for i=1,Inputs do
|
||||
network.neurons[i] = newNeuron()
|
||||
end
|
||||
|
||||
for o=1,Outputs do
|
||||
if o == 4 then
|
||||
goto continue
|
||||
end
|
||||
|
||||
network.neurons[config.NeatConfig.MaxNodes+o] = newNeuron()
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
table.sort(genome.genes, function (a,b)
|
||||
return (a.out < b.out)
|
||||
end)
|
||||
for i=1,#genome.genes do
|
||||
local gene = genome.genes[i]
|
||||
if gene.enabled then
|
||||
if network.neurons[gene.out] == nil then
|
||||
network.neurons[gene.out] = newNeuron()
|
||||
end
|
||||
local neuron = network.neurons[gene.out]
|
||||
table.insert(neuron.incoming, gene)
|
||||
if network.neurons[gene.into] == nil then
|
||||
network.neurons[gene.into] = newNeuron()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
genome.network = network
|
||||
end
|
||||
|
||||
|
||||
local function initializeRun(_M)
|
||||
message(_M, string.format("Total Genomes: %d", #_M.currentSpecies.genomes))
|
||||
|
||||
settings.set_speed("turbo")
|
||||
|
@ -419,12 +485,46 @@ local function initializeRun(_M, after)
|
|||
end
|
||||
exec('enable-sound '..enableSound)
|
||||
gui.subframe_update(false)
|
||||
table.insert(_M.runInitialized, after)
|
||||
movie.unsafe_rewind(rew)
|
||||
|
||||
return rewind():next(function()
|
||||
if config.StartPowerup ~= nil then
|
||||
game.writePowerup(config.StartPowerup)
|
||||
end
|
||||
_M.currentFrame = 0
|
||||
_M.timeout = config.NeatConfig.TimeoutConstant
|
||||
-- Kill the run if we go back to the map screen
|
||||
game.onceMapLoaded(function()
|
||||
_M.timeout = -100000
|
||||
end)
|
||||
_M.bumps = 0
|
||||
-- Penalize player for collisions that do not result in enemy deaths
|
||||
game.onEmptyHit(function()
|
||||
_M.bumps = _M.bumps + 1
|
||||
end)
|
||||
game.clearJoypad()
|
||||
_M.startKong = game.getKong()
|
||||
_M.startBananas = game.getBananas()
|
||||
_M.startKrem = game.getKremCoins()
|
||||
_M.lastKrem = _M.startKrem
|
||||
_M.startCoins = game.getCoins()
|
||||
_M.startLives = game.getLives()
|
||||
_M.partyHitCounter = 0
|
||||
_M.powerUpCounter = 0
|
||||
_M.powerUpBefore = game.getBoth()
|
||||
_M.currentArea = game.getCurrentArea()
|
||||
_M.lastArea = _M.currentArea
|
||||
_M.rightmost = { [_M.currentArea] = 0 }
|
||||
_M.upmost = { [_M.currentArea] = 0 }
|
||||
local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
|
||||
generateNetwork(genome)
|
||||
evaluateCurrent(_M)
|
||||
end)
|
||||
end
|
||||
|
||||
local frame = 0
|
||||
|
||||
local function mainLoop(_M, genome)
|
||||
advanceFrame(_M, function()
|
||||
return advanceFrame(_M):next(function()
|
||||
if genome ~= nil then
|
||||
_M.currentFrame = _M.currentFrame + 1
|
||||
end
|
||||
|
@ -432,11 +532,7 @@ local function mainLoop(_M, genome)
|
|||
genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
|
||||
|
||||
if _M.drawFrame % 10 == 0 then
|
||||
--if not pcall(function()
|
||||
displayGenome(genome)
|
||||
--end) then
|
||||
--message(_M, "Could not render genome graph", 0x00990000)
|
||||
--end
|
||||
end
|
||||
|
||||
if _M.currentFrame%5 == 0 then
|
||||
|
@ -529,8 +625,7 @@ local function mainLoop(_M, genome)
|
|||
-- Continue if we haven't timed out
|
||||
local timeoutBonus = _M.currentFrame / 4
|
||||
if _M.timeout + timeoutBonus > 0 then
|
||||
mainLoop(_M, genome)
|
||||
return
|
||||
return mainLoop(_M, genome)
|
||||
end
|
||||
|
||||
-- Timeout calculations beyond this point
|
||||
|
@ -609,103 +704,16 @@ local function mainLoop(_M, genome)
|
|||
input.keyhook("9", false)
|
||||
input.keyhook("tab", false)
|
||||
|
||||
_M.finishCallback()
|
||||
return
|
||||
end
|
||||
end
|
||||
initializeRun(_M, function()
|
||||
mainLoop(_M, genome)
|
||||
|
||||
return initializeRun(_M):next(function()
|
||||
return mainLoop(_M, genome)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local function newNeuron()
|
||||
local neuron = {}
|
||||
neuron.incoming = {}
|
||||
neuron.value = 0.0
|
||||
--neuron.dw = 1
|
||||
return neuron
|
||||
end
|
||||
|
||||
local function generateNetwork(genome)
|
||||
local network = {}
|
||||
network.neurons = {}
|
||||
|
||||
for i=1,Inputs do
|
||||
network.neurons[i] = newNeuron()
|
||||
end
|
||||
|
||||
for o=1,Outputs do
|
||||
if o == 4 then
|
||||
goto continue
|
||||
end
|
||||
|
||||
network.neurons[config.NeatConfig.MaxNodes+o] = newNeuron()
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
table.sort(genome.genes, function (a,b)
|
||||
return (a.out < b.out)
|
||||
end)
|
||||
for i=1,#genome.genes do
|
||||
local gene = genome.genes[i]
|
||||
if gene.enabled then
|
||||
if network.neurons[gene.out] == nil then
|
||||
network.neurons[gene.out] = newNeuron()
|
||||
end
|
||||
local neuron = network.neurons[gene.out]
|
||||
table.insert(neuron.incoming, gene)
|
||||
if network.neurons[gene.into] == nil then
|
||||
network.neurons[gene.into] = newNeuron()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
genome.network = network
|
||||
end
|
||||
|
||||
local function elapsed(_M)
|
||||
if config.StartPowerup ~= nil then
|
||||
game.writePowerup(config.StartPowerup)
|
||||
end
|
||||
_M.currentFrame = 0
|
||||
_M.timeout = config.NeatConfig.TimeoutConstant
|
||||
-- Kill the run if we go back to the map screen
|
||||
game.onceMapLoaded(function()
|
||||
_M.timeout = -100000
|
||||
end)
|
||||
_M.bumps = 0
|
||||
-- Penalize player for collisions that do not result in enemy deaths
|
||||
game.onEmptyHit(function()
|
||||
_M.bumps = _M.bumps + 1
|
||||
end)
|
||||
game.clearJoypad()
|
||||
_M.startKong = game.getKong()
|
||||
_M.startBananas = game.getBananas()
|
||||
_M.startKrem = game.getKremCoins()
|
||||
_M.lastKrem = _M.startKrem
|
||||
_M.startCoins = game.getCoins()
|
||||
_M.startLives = game.getLives()
|
||||
_M.partyHitCounter = 0
|
||||
_M.powerUpCounter = 0
|
||||
_M.powerUpBefore = game.getBoth()
|
||||
_M.currentArea = game.getCurrentArea()
|
||||
_M.lastArea = _M.currentArea
|
||||
_M.rightmost = { [_M.currentArea] = 0 }
|
||||
_M.upmost = { [_M.currentArea] = 0 }
|
||||
local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
|
||||
generateNetwork(genome)
|
||||
evaluateCurrent(_M)
|
||||
for i=#_M.runInitialized,1,-1 do
|
||||
table.remove(_M.runInitialized, i)()
|
||||
end
|
||||
end
|
||||
|
||||
local function rewound()
|
||||
set_timer_timeout(1)
|
||||
end
|
||||
|
||||
local function register(_M, name, func)
|
||||
callback.register(name, func)
|
||||
_M.dereg[#_M.dereg+1] = { name, func }
|
||||
|
@ -740,7 +748,7 @@ local function keyhook (_M, key, state)
|
|||
if key == "tab" then
|
||||
_M.inputmode = not _M.inputmode
|
||||
_M.helddown = key
|
||||
elseif inputmode then
|
||||
elseif _M.inputmode then
|
||||
return
|
||||
elseif key == "1" then
|
||||
_M.helddown = key
|
||||
|
@ -761,13 +769,13 @@ local function keyhook (_M, key, state)
|
|||
pool.run(true)
|
||||
end
|
||||
elseif state.value == 0 then
|
||||
helddown = nil
|
||||
_M.helddown = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function saveLoadInput(_M)
|
||||
local inputs = input.raw()
|
||||
if not inputmode then
|
||||
if not _M.inputmode then
|
||||
-- FIXME
|
||||
_M.saveLoadFile = config.NeatConfig.SaveFile
|
||||
return
|
||||
|
@ -829,30 +837,25 @@ local function saveLoadInput(_M)
|
|||
end
|
||||
end
|
||||
|
||||
local function run(_M, species, generationIdx, genomeCallback, finishCallback)
|
||||
local function run(_M, species, generationIdx, genomeCallback)
|
||||
game.registerHandlers()
|
||||
|
||||
_M.currentGenerationIndex = generationIdx
|
||||
_M.currentSpecies = species
|
||||
_M.currentGenomeIndex = 1
|
||||
_M.genomeCallback = genomeCallback
|
||||
_M.finishCallback = finishCallback
|
||||
register(_M, 'paint', function()
|
||||
painting(_M)
|
||||
end)
|
||||
register(_M, 'input', function()
|
||||
frame = frame + 1
|
||||
processFrameAdvanced(_M)
|
||||
end)
|
||||
register(_M, 'input', function()
|
||||
saveLoadInput(_M)
|
||||
end)
|
||||
register(_M, 'keyhook', function(key, state)
|
||||
keyhook(_M, key, state)
|
||||
end)
|
||||
register(_M, 'post_rewind', rewound)
|
||||
register(_M, 'timer', function()
|
||||
elapsed(_M)
|
||||
end)
|
||||
|
||||
input.keyhook("1", true)
|
||||
input.keyhook("4", true)
|
||||
|
@ -861,8 +864,8 @@ local function run(_M, species, generationIdx, genomeCallback, finishCallback)
|
|||
input.keyhook("9", true)
|
||||
input.keyhook("tab", true)
|
||||
|
||||
initializeRun(_M, function()
|
||||
mainLoop(_M)
|
||||
return initializeRun(_M):next(function()
|
||||
return mainLoop(_M)
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -874,11 +877,11 @@ local function onRenderForm(_M, handler)
|
|||
table.insert(_M.onRenderFormHandler, handler)
|
||||
end
|
||||
|
||||
return function()
|
||||
return function(promise)
|
||||
Promise = promise
|
||||
local _M = {
|
||||
currentGenerationIndex = 1,
|
||||
currentSpecies = nil,
|
||||
finishCallback = nil,
|
||||
genomeCallback = nil,
|
||||
currentGenomeIndex = 1,
|
||||
currentFrame = 0,
|
||||
|
@ -907,8 +910,6 @@ return function()
|
|||
upmost = {},
|
||||
lastBoth = 0,
|
||||
|
||||
runInitialized = {},
|
||||
|
||||
onMessageHandler = {},
|
||||
onSaveHandler = {},
|
||||
onLoadHandler = {},
|
||||
|
@ -933,8 +934,8 @@ return function()
|
|||
onLoad(_M, handler)
|
||||
end
|
||||
|
||||
_M.run = function(species, generationIdx, genomeCallback, finishCallback)
|
||||
run(_M, species, generationIdx, genomeCallback, finishCallback)
|
||||
_M.run = function(species, generationIdx, genomeCallback)
|
||||
return run(_M, species, generationIdx, genomeCallback)
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1").."/.."
|
||||
|
||||
local util = dofile(base.."/util.lua")
|
||||
local util = dofile(base.."/util.lua")()
|
||||
local config = dofile(base.."/config.lua")
|
||||
local mem = dofile(base.."/mem.lua")
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ local warn = '========== The ROM must be running before running this script'
|
|||
io.stderr:write(warn)
|
||||
print(warn)
|
||||
|
||||
local util = dofile(base.."/util.lua")
|
||||
local util = dofile(base.."/util.lua")()
|
||||
local mem = dofile(base.."/mem.lua")
|
||||
local spritelist = dofile(base.."/spritelist.lua")
|
||||
local game = dofile(base.."/game.lua")
|
||||
|
@ -436,3 +436,17 @@ input.keyhook("9", true)
|
|||
input.keyhook("0", true)
|
||||
|
||||
set_timer_timeout(100 * 1000)
|
||||
|
||||
for i=0,22,1 do
|
||||
memory2.BUS:registerwrite(mem.addr.spriteBase + mem.size.sprite * i + mem.offset.sprite.x, function(addr, val)
|
||||
print(memory.getregister('pc'))
|
||||
end)
|
||||
end
|
||||
|
||||
-- fe0a58 crate: near bunch and klomp on barrels
|
||||
-- fe0a58: Crate X position
|
||||
-- fe0a60: Crate Y position
|
||||
|
||||
-- fe0a70 bunch: near crate and klomp on barrels
|
||||
-- fe0a70: X position
|
||||
-- fe0a72: Y position
|
53
util.lua
53
util.lua
|
@ -2,6 +2,8 @@ local utime = utime
|
|||
|
||||
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
|
||||
|
||||
local Promise = nil
|
||||
|
||||
local _M = {}
|
||||
|
||||
_M.isWin = package.config:sub(1, 1) == '\\'
|
||||
|
@ -102,27 +104,10 @@ function _M.closeCmd(handle)
|
|||
end
|
||||
end
|
||||
|
||||
function _M.finishWaiting(waiter, count)
|
||||
if count == nil then
|
||||
count = 1
|
||||
end
|
||||
function _M.waitForFiles(filenames, count, wild)
|
||||
local promise = Promise.new()
|
||||
promise:resolve()
|
||||
|
||||
if _M.isWin then
|
||||
local i = 1
|
||||
while i <= count do
|
||||
local line = waiter:read("*l")
|
||||
for chr in line:gmatch(";") do
|
||||
i = i + 1
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
else
|
||||
waiter:read("*a")
|
||||
_M.closeCmd(waiter)
|
||||
end
|
||||
end
|
||||
|
||||
function _M.startWaiting(filenames, count, wild)
|
||||
if type(filenames) == 'string' then
|
||||
if wild == nil then
|
||||
wild = filenames
|
||||
|
@ -135,16 +120,15 @@ function _M.startWaiting(filenames, count, wild)
|
|||
count = #filenames
|
||||
end
|
||||
|
||||
local poppet = nil
|
||||
if _M.isWin then
|
||||
local sec, usec = utime()
|
||||
print(string.format('Starting watching file at %d', sec * 1000000 + usec))
|
||||
|
||||
local cmd = '"'..base..'/watchexec/watchexec.exe" "-w" "'..table.concat(filenames, '" "-w" "')..'" "echo" "%WATCHEXEC_WRITTEN_PATH%"'
|
||||
local poppet = _M.popenCmd(cmd, base)
|
||||
poppet = _M.popenCmd(cmd, base)
|
||||
|
||||
poppet:read("*l")
|
||||
|
||||
return poppet
|
||||
else
|
||||
local watchCmd = ''
|
||||
if count == 1 then
|
||||
|
@ -167,8 +151,24 @@ done ) &
|
|||
wait
|
||||
EOF]]
|
||||
end
|
||||
return _M.popenCmd(watchCmd)
|
||||
poppet = _M.popenCmd(watchCmd)
|
||||
end
|
||||
|
||||
return promise:next(function()
|
||||
if _M.isWin then
|
||||
local i = 1
|
||||
while i <= count do
|
||||
local line = poppet:read("*l")
|
||||
for chr in line:gmatch(";") do
|
||||
i = i + 1
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
else
|
||||
print(poppet:read("*a"))
|
||||
_M.closeCmd(poppet)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function _M.table_to_string(tbl)
|
||||
|
@ -214,4 +214,7 @@ function _M.file_exists(name)
|
|||
if f~=nil then io.close(f) return true else return false end
|
||||
end
|
||||
|
||||
return _M
|
||||
return function(promise)
|
||||
Promise = promise
|
||||
return _M
|
||||
end
|
Loading…
Add table
Reference in a new issue