Promisify everything?

This commit is contained in:
Empathic Qubit 2021-05-01 19:39:35 -04:00
parent 088c92113b
commit c7819c98f2
10 changed files with 487 additions and 414 deletions

View file

@ -6,7 +6,7 @@ local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local mathFunctions = dofile(base.."/mathFunctions.lua") local mathFunctions = dofile(base.."/mathFunctions.lua")
local config = dofile(base.."/config.lua") local config = dofile(base.."/config.lua")
local spritelist = dofile(base.."/spritelist.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 mem = dofile(base.."/mem.lua")
local _M = { local _M = {
leader = 0, leader = 0,

View file

@ -4,7 +4,6 @@ local gui = gui
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1") local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local pool = dofile(base.."/pool.lua") local pool = dofile(base.."/pool.lua")
local util = dofile(base.."/util.lua")
local statusLine = nil local statusLine = nil
local statusColor = 0x0000ff00 local statusColor = 0x0000ff00
@ -30,4 +29,10 @@ pool.onRenderForm(function(form)
gui.text(-500, guiHeight - 20, statusLine, 0x00000000) gui.text(-500, guiHeight - 20, statusLine, 0x00000000)
end end
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
View file

@ -1,7 +1,17 @@
local callback, set_timer_timeout = callback, set_timer_timeout
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1") 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 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 serpent = dofile(base.."/serpent.lua")
local libDeflate = dofile(base.."/LibDeflate.lua") local libDeflate = dofile(base.."/LibDeflate.lua")
@ -386,15 +396,17 @@ local function addToSpecies(child)
end end
end end
local function initializePool(after) local function initializePool()
local promise = Promise.new()
promise:resolve()
return promise:next(function()
pool = newPool() pool = newPool()
for i=1,config.NeatConfig.Population do for i=1,config.NeatConfig.Population do
basic = basicGenome() local basic = basicGenome()
addToSpecies(basic) addToSpecies(basic)
end end
end)
after()
end end
local function bytes(x) local function bytes(x)
@ -406,6 +418,9 @@ local function bytes(x)
end end
local function writeFile(filename) local function writeFile(filename)
local promise = Promise.new()
promise:resolve()
return promise:next(function ()
local file = io.open(filename, "w") local file = io.open(filename, "w")
local dump = serpent.dump(pool) local dump = serpent.dump(pool)
local zlib = libDeflate:CompressDeflate(dump) local zlib = libDeflate:CompressDeflate(dump)
@ -414,11 +429,14 @@ local function writeFile(filename)
file:write(string.char(0,0,0,0)) file:write(string.char(0,0,0,0))
file:write(bytes(#dump % (2^32))) file:write(bytes(#dump % (2^32)))
file:close() file:close()
return end)
end end
-- FIXME Save/load mechanism has to be rethought with items running in parallel -- FIXME This isn't technically asynchronous. Probably can't be though.
local function loadFile(filename, after) local function loadFile(filename)
local promise = Promise.new()
promise:resolve()
return promise:next(function()
message("Loading pool from " .. filename, 0x00999900) message("Loading pool from " .. filename, 0x00999900)
local file = io.open(filename, "r") local file = io.open(filename, "r")
if file == nil then if file == nil then
@ -433,6 +451,7 @@ local function loadFile(filename, after)
end end
pool = obj pool = obj
end)
end end
local function savePool() local function savePool()
@ -441,9 +460,8 @@ local function savePool()
message(string.format("Saved \"%s\"!", filename:sub(#filename - 50)), 0x00009900) message(string.format("Saved \"%s\"!", filename:sub(#filename - 50)), 0x00009900)
end end
local function loadPool(after) local function loadPool()
loadFile(_M.saveLoadFile, after) return loadFile(_M.saveLoadFile)
after()
end end
local function processRenderForm(form) local function processRenderForm(form)
@ -471,7 +489,7 @@ end
local function crossover(g1, g2) local function crossover(g1, g2)
-- Make sure g1 is the higher fitness genome -- Make sure g1 is the higher fitness genome
if g2.fitness > g1.fitness then if g2.fitness > g1.fitness then
tempg = g1 local tempg = g1
g1 = g2 g1 = g2
g2 = tempg g2 = tempg
end end
@ -562,11 +580,11 @@ end
local function breedChild(species) local function breedChild(species)
local child = {} local child = {}
if math.random() < config.NeatConfig.CrossoverChance then if math.random() < config.NeatConfig.CrossoverChance then
g1 = species.genomes[math.random(1, #species.genomes)] local g1 = species.genomes[math.random(1, #species.genomes)]
g2 = species.genomes[math.random(1, #species.genomes)] local g2 = species.genomes[math.random(1, #species.genomes)]
child = crossover(g1, g2) child = crossover(g1, g2)
else else
g = species.genomes[math.random(1, #species.genomes)] local g = species.genomes[math.random(1, #species.genomes)]
child = copyGenome(g) child = copyGenome(g)
end end
@ -605,7 +623,7 @@ local function removeWeakSpecies()
local sum = totalAverageFitness() local sum = totalAverageFitness()
for s = 1,#pool.species do for s = 1,#pool.species do
local species = pool.species[s] 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 if breed >= 1 then
table.insert(survived, species) table.insert(survived, species)
end end
@ -628,7 +646,7 @@ local function newGeneration()
local children = {} local children = {}
for s = 1,#pool.species do for s = 1,#pool.species do
local species = pool.species[s] 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 for i=1,breed do
table.insert(children, breedChild(species)) table.insert(children, breedChild(species))
end end
@ -652,7 +670,7 @@ local function newGeneration()
writeFile(_M.saveLoadFile .. ".gen" .. pool.generation .. ".pool") writeFile(_M.saveLoadFile .. ".gen" .. pool.generation .. ".pool")
end end
local runner = Runner() local runner = Runner(Promise)
runner.onMessage(function(msg, color) runner.onMessage(function(msg, color)
message(msg, color) message(msg, color)
end) end)
@ -671,11 +689,20 @@ local topRequested = false
local loadRequested = false local loadRequested = false
local saveRequested = 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 if loadRequested then
loadRequested = false loadRequested = false
loadPool(mainLoop) currentSpecies = nil
return -- FIXME
return loadPool()
end end
if saveRequested then if saveRequested then
@ -685,19 +712,13 @@ local function mainLoop(currentSpecies)
if topRequested then if topRequested then
topRequested = false topRequested = false
playTop() return playTop()
return
end end
if not config.Running then if not config.Running then
-- FIXME Tick? -- FIXME Tick?
end end
if currentSpecies == nil then
currentSpecies = 1
end
local slice = pool.species[currentSpecies]
if hasThreads then if hasThreads then
slice = {} slice = {}
for i=currentSpecies, currentSpecies + config.NeatConfig.Threads - 1, 1 do for i=currentSpecies, currentSpecies + config.NeatConfig.Threads - 1, 1 do
@ -709,18 +730,16 @@ local function mainLoop(currentSpecies)
end end
end end
local finished = 0 local finished = 0
runner.run(
return runner.run(
slice, slice,
pool.generation, pool.generation,
function() function()
-- Genome callback -- Genome callback
end, -- FIXME Should we do something here??? What was your plan, past me?
function()
if hasThreads then
finished = finished + 1
if finished ~= #slice then
return
end end
):next(function()
if hasThreads then
currentSpecies = currentSpecies + #slice currentSpecies = currentSpecies + #slice
else else
currentSpecies = currentSpecies + 1 currentSpecies = currentSpecies + 1
@ -730,9 +749,12 @@ local function mainLoop(currentSpecies)
newGeneration() newGeneration()
currentSpecies = 1 currentSpecies = 1
end end
mainLoop(currentSpecies) end)
end):next(function ()
if topGenome == nil then
return mainLoop(currentSpecies)
end end
) end)
end end
playTop = function() playTop = function()
@ -749,7 +771,7 @@ playTop = function()
end end
-- FIXME genome -- FIXME genome
mainLoop(maxs) return mainLoop(maxs, maxg)
end end
function _M.requestLoad(filename) function _M.requestLoad(filename)
@ -775,15 +797,19 @@ function _M.requestTop()
end end
function _M.run(reset) function _M.run(reset)
local promise = nil
if pool == nil or reset == true then if pool == nil or reset == true then
initializePool(function() promise = initializePool()
writeFile(config.PoolDir.."temp.pool")
mainLoop()
end)
else else
writeFile(config.PoolDir.."temp.pool") promise = Promise.new()
mainLoop() promise:resolve()
end end
return promise:next(function()
return writeFile(config.PoolDir.."temp.pool")
end):next(function ()
return mainLoop()
end)
end end
return _M return _M

View file

@ -78,7 +78,6 @@ transition = function(promise, state, value)
if promise.state == state if promise.state == state
or promise.state ~= State.PENDING or promise.state ~= State.PENDING
or ( state ~= State.FULFILLED and state ~= State.REJECTED ) or ( state ~= State.FULFILLED and state ~= State.REJECTED )
or value == nil
then then
return return
end end

View file

@ -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 base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local Promise = dofile(base.."/promise.lua") 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 Runner = dofile(base.."/runner.lua")
local serpent = dofile(base.."/serpent.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 inputFilePath = os.getenv("RUNNER_INPUT_FILE")
local outputFilePath = os.getenv("RUNNER_OUTPUT_FILE") local outputFilePath = os.getenv("RUNNER_OUTPUT_FILE")
local first = false
local outContents = {} local outContents = {}
local statusLine = nil local statusLine = nil
@ -22,7 +26,7 @@ local species = nil
local speciesId = -1 local speciesId = -1
local generationIndex = nil local generationIndex = nil
local runner = Runner() local runner = Runner(Promise)
runner.onMessage(function(msg, color) runner.onMessage(function(msg, color)
statusLine = msg statusLine = msg
statusColor = color statusColor = color
@ -78,32 +82,7 @@ runner.onLoad(function(filename)
) )
end) end)
local waiter = nil
if not util.isWin then
waiter = util.startWaiting(inputFilePath)
end
local function waitLoop() 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 inputData = nil
local ok = false local ok = false
while not ok or inputData == nil or speciesId == inputData[1].id do while not ok or inputData == nil or speciesId == inputData[1].id do
@ -128,7 +107,7 @@ local function waitLoop()
print('Running') print('Running')
runner.run( return runner.run(
species, species,
generationIndex, generationIndex,
function(genome, index) function(genome, index)
@ -141,8 +120,8 @@ local function waitLoop()
speciesId = speciesId, speciesId = speciesId,
}) })
) )
end, end
function() ):next(function()
table.insert( table.insert(
outContents, outContents,
serpent.dump({ serpent.dump({
@ -156,8 +135,12 @@ local function waitLoop()
local inputFile = io.open(inputFilePath, "w") local inputFile = io.open(inputFilePath, "w")
inputFile:close() inputFile:close()
if not util.isWin then local waiter = nil
waiter = util.startWaiting(inputFilePath) if util.isWin then
waiter = Promise.new()
waiter:resolve()
else
waiter = util.waitForFiles(inputFilePath)
end end
-- Write the result -- Write the result
@ -165,9 +148,27 @@ local function waitLoop()
outFile:write(table.concat(outContents, "\n")) outFile:write(table.concat(outContents, "\n"))
outFile:close() outFile:close()
waitLoop() return waiter
end end):next(waitLoop)
)
end 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)

View file

@ -2,7 +2,9 @@ local random = random
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1") 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 config = dofile(base.."/config.lua")
local serpent = dofile(base.."/serpent.lua") local serpent = dofile(base.."/serpent.lua")
local temps = { 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)))..
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) local function message(_M, msg, color)
if color == nil then if color == nil then
color = 0x00009900 color = 0x00009900
@ -59,7 +64,46 @@ local function onMessage(_M, handler)
table.insert(_M.onMessageHandler, handler) table.insert(_M.onMessageHandler, handler)
end 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 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.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) util.unzip(base..'/watchexec.zip', base)
@ -102,12 +146,12 @@ return function()
onLoad(_M, handler) onLoad(_M, handler)
end end
_M.run = function(species, generationIdx, genomeCallback, finishCallback) _M.run = function(speciesSlice, generationIdx, genomeCallback)
local inputPrefix = tmpFileName..'_input_' local promise = Promise.new()
local outputPrefix = tmpFileName..'_output_' promise:resolve()
return promise:next(function()
-- Create the input files and output files -- Create the input files and output files
for i=1,#species,1 do for i=1,#speciesSlice,1 do
local inputFileName = inputPrefix..i local inputFileName = inputPrefix..i
local inputFile = io.open(inputFileName, 'a') local inputFile = io.open(inputFileName, 'a')
inputFile:close() inputFile:close()
@ -117,58 +161,33 @@ return function()
outputFile:close() outputFile:close()
end end
while #_M.poppets < #species do return launchChildren(_M, #speciesSlice)
local i = #_M.poppets+1 end):next(function()
local outputFileName = outputPrefix..i local outputFileNames = {}
local inputFileName = inputPrefix..i for i=1,#speciesSlice,1 do
table.insert(outputFileNames, outputPrefix..i)
message(_M, _M.hostProcess)
local settingsDir = nil
if isWin then
settingsDir = tempDir.."/donk_runner_settings_"..i
util.mkdir(settingsDir)
end end
local envs = { local waiter = util.waitForFiles(outputFileNames, nil, tmpFileName.."_output_*")
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_*")
message(_M, 'Setting up child processes') message(_M, 'Setting up child processes')
for i=1,#species,1 do for i=1,#speciesSlice,1 do
local inputFileName = tmpFileName.."_input_"..i local inputFileName = tmpFileName.."_input_"..i
local inputFile = io.open(inputFileName, 'w') local inputFile = io.open(inputFileName, 'w')
inputFile:write(serpent.dump({species[i], generationIdx})) inputFile:write(serpent.dump({speciesSlice[i], generationIdx}))
inputFile:close() inputFile:close()
end end
message(_M, 'Waiting for child processes to finish') message(_M, 'Waiting for child processes to finish')
util.finishWaiting(waiter, #waiters) return waiter
end):next(function()
message(_M, 'Child processes finished') 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) message(_M, "Processing output "..i)
local outputFileName = tmpFileName..'_output_'..i local outputFileName = tmpFileName..'_output_'..i
local outputFile = io.open(outputFileName, "r") local outputFile = io.open(outputFileName, "r")
@ -190,8 +209,8 @@ return function()
elseif obj.type == 'onSave' then elseif obj.type == 'onSave' then
save(_M, obj.filename) save(_M, obj.filename)
elseif obj.type == 'onGenome' then elseif obj.type == 'onGenome' then
for i=1,#species,1 do for i=1,#speciesSlice,1 do
local s = species[i] local s = speciesSlice[i]
if s.id == obj.speciesId then if s.id == obj.speciesId then
s.genomes[obj.genomeIndex] = obj.genome s.genomes[obj.genomeIndex] = obj.genome
break break
@ -199,14 +218,19 @@ return function()
end end
genomeCallback(obj.genome, obj.index) genomeCallback(obj.genome, obj.index)
elseif obj.type == 'onFinish' then elseif obj.type == 'onFinish' then
finishCallback() finished = finished + 1
if finished == #speciesSlice then
outputFile:close()
return
end
end end
::continue:: ::continue::
line = outputFile:read() line = outputFile:read()
until(line == "" or line == nil) until(line == "" or line == nil)
outputFile:close()
end end
error(string.format("Some processes never finished? Saw %d terminations.", finished))
end)
end end
return _M return _M

View file

@ -12,6 +12,8 @@ local Outputs = #config.ButtonNames
local guiWidth = 0 local guiWidth = 0
local guiHeight = 0 local guiHeight = 0
local Promise = nil
local function message(_M, msg, color) local function message(_M, msg, color)
if color == nil then if color == nil then
color = 0x00009900 color = 0x00009900
@ -213,19 +215,21 @@ local function displayGenome(genome)
gui.renderctx.setnull() gui.renderctx.setnull()
end end
local function advanceFrame(_M, after) local function advanceFrame(_M)
table.insert(_M.onFrameAdvancedHandler, after) local promise = Promise.new()
table.insert(_M.onFrameAdvancedHandler, promise)
return promise
end end
local function processFrameAdvanced(_M) local function processFrameAdvanced(_M)
for i=#_M.onFrameAdvancedHandler,1,-1 do for i=#_M.onFrameAdvancedHandler,1,-1 do
table.remove(_M.onFrameAdvancedHandler, i)() table.remove(_M.onFrameAdvancedHandler, i):resolve()
end end
end end
local buttons = nil local buttons = nil
local buttonCtx = gui.renderctx.new(500, 70) local buttonCtx = gui.renderctx.new(500, 70)
function displayButtons() local function displayButtons(_M)
buttonCtx:set() buttonCtx:set()
buttonCtx:clear() buttonCtx:clear()
@ -249,7 +253,7 @@ function displayButtons()
local insert = "" local insert = ""
local confirm = "[Tab] Type in filename" local confirm = "[Tab] Type in filename"
if inputmode then if _M.inputmode then
insert = "_" insert = "_"
confirm = "[Tab] Confirm filename" confirm = "[Tab] Confirm filename"
end end
@ -264,7 +268,7 @@ end
local formCtx = nil local formCtx = nil
local form = nil local form = nil
function displayForm(_M) local function displayForm(_M)
if #_M.onRenderFormHandler == 0 then if #_M.onRenderFormHandler == 0 then
return return
end end
@ -303,7 +307,7 @@ function displayForm(_M)
gui.text(320, 65, string.format("Current Area: %04x", _M.currentArea)) gui.text(320, 65, string.format("Current Area: %04x", _M.currentArea))
gui.text(320, 80, "Rightmost: "..rightmost) gui.text(320, 80, "Rightmost: "..rightmost)
displayButtons() displayButtons(_M)
formCtx:set() formCtx:set()
buttons:draw(5, 130) buttons:draw(5, 130)
@ -373,11 +377,12 @@ local function evaluateNetwork(_M, network, inputs, inputDeltas)
return outputs return outputs
end end
local controller = nil
local function evaluateCurrent(_M) local function evaluateCurrent(_M)
local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex] local genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
local inputDeltas = {} local inputDeltas = {}
inputs, inputDeltas = game.getInputs() local inputs, inputDeltas = game.getInputs()
controller = evaluateNetwork(_M, genome.network, inputs, inputDeltas) controller = evaluateNetwork(_M, genome.network, inputs, inputDeltas)
@ -405,9 +410,70 @@ local function fitnessAlreadyMeasured(_M)
return genome.fitness ~= 0 return genome.fitness ~= 0
end end
local rewinds = {}
local rew = movie.to_rewind(config.NeatConfig.Filename) 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)) message(_M, string.format("Total Genomes: %d", #_M.currentSpecies.genomes))
settings.set_speed("turbo") settings.set_speed("turbo")
@ -419,12 +485,46 @@ local function initializeRun(_M, after)
end end
exec('enable-sound '..enableSound) exec('enable-sound '..enableSound)
gui.subframe_update(false) 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 end
local frame = 0
local function mainLoop(_M, genome) local function mainLoop(_M, genome)
advanceFrame(_M, function() return advanceFrame(_M):next(function()
if genome ~= nil then if genome ~= nil then
_M.currentFrame = _M.currentFrame + 1 _M.currentFrame = _M.currentFrame + 1
end end
@ -432,11 +532,7 @@ local function mainLoop(_M, genome)
genome = _M.currentSpecies.genomes[_M.currentGenomeIndex] genome = _M.currentSpecies.genomes[_M.currentGenomeIndex]
if _M.drawFrame % 10 == 0 then if _M.drawFrame % 10 == 0 then
--if not pcall(function()
displayGenome(genome) displayGenome(genome)
--end) then
--message(_M, "Could not render genome graph", 0x00990000)
--end
end end
if _M.currentFrame%5 == 0 then if _M.currentFrame%5 == 0 then
@ -529,8 +625,7 @@ local function mainLoop(_M, genome)
-- Continue if we haven't timed out -- Continue if we haven't timed out
local timeoutBonus = _M.currentFrame / 4 local timeoutBonus = _M.currentFrame / 4
if _M.timeout + timeoutBonus > 0 then if _M.timeout + timeoutBonus > 0 then
mainLoop(_M, genome) return mainLoop(_M, genome)
return
end end
-- Timeout calculations beyond this point -- Timeout calculations beyond this point
@ -609,103 +704,16 @@ local function mainLoop(_M, genome)
input.keyhook("9", false) input.keyhook("9", false)
input.keyhook("tab", false) input.keyhook("tab", false)
_M.finishCallback()
return return
end end
end end
initializeRun(_M, function()
mainLoop(_M, genome) return initializeRun(_M):next(function()
return mainLoop(_M, genome)
end) end)
end) 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) local function register(_M, name, func)
callback.register(name, func) callback.register(name, func)
_M.dereg[#_M.dereg+1] = { name, func } _M.dereg[#_M.dereg+1] = { name, func }
@ -740,7 +748,7 @@ local function keyhook (_M, key, state)
if key == "tab" then if key == "tab" then
_M.inputmode = not _M.inputmode _M.inputmode = not _M.inputmode
_M.helddown = key _M.helddown = key
elseif inputmode then elseif _M.inputmode then
return return
elseif key == "1" then elseif key == "1" then
_M.helddown = key _M.helddown = key
@ -761,13 +769,13 @@ local function keyhook (_M, key, state)
pool.run(true) pool.run(true)
end end
elseif state.value == 0 then elseif state.value == 0 then
helddown = nil _M.helddown = nil
end end
end end
local function saveLoadInput(_M) local function saveLoadInput(_M)
local inputs = input.raw() local inputs = input.raw()
if not inputmode then if not _M.inputmode then
-- FIXME -- FIXME
_M.saveLoadFile = config.NeatConfig.SaveFile _M.saveLoadFile = config.NeatConfig.SaveFile
return return
@ -829,30 +837,25 @@ local function saveLoadInput(_M)
end end
end end
local function run(_M, species, generationIdx, genomeCallback, finishCallback) local function run(_M, species, generationIdx, genomeCallback)
game.registerHandlers() game.registerHandlers()
_M.currentGenerationIndex = generationIdx _M.currentGenerationIndex = generationIdx
_M.currentSpecies = species _M.currentSpecies = species
_M.currentGenomeIndex = 1 _M.currentGenomeIndex = 1
_M.genomeCallback = genomeCallback _M.genomeCallback = genomeCallback
_M.finishCallback = finishCallback
register(_M, 'paint', function() register(_M, 'paint', function()
painting(_M) painting(_M)
end) end)
register(_M, 'input', function() register(_M, 'input', function()
frame = frame + 1
processFrameAdvanced(_M) processFrameAdvanced(_M)
end)
register(_M, 'input', function()
saveLoadInput(_M) saveLoadInput(_M)
end) end)
register(_M, 'keyhook', function(key, state) register(_M, 'keyhook', function(key, state)
keyhook(_M, key, state) keyhook(_M, key, state)
end) end)
register(_M, 'post_rewind', rewound) register(_M, 'post_rewind', rewound)
register(_M, 'timer', function()
elapsed(_M)
end)
input.keyhook("1", true) input.keyhook("1", true)
input.keyhook("4", true) input.keyhook("4", true)
@ -861,8 +864,8 @@ local function run(_M, species, generationIdx, genomeCallback, finishCallback)
input.keyhook("9", true) input.keyhook("9", true)
input.keyhook("tab", true) input.keyhook("tab", true)
initializeRun(_M, function() return initializeRun(_M):next(function()
mainLoop(_M) return mainLoop(_M)
end) end)
end end
@ -874,11 +877,11 @@ local function onRenderForm(_M, handler)
table.insert(_M.onRenderFormHandler, handler) table.insert(_M.onRenderFormHandler, handler)
end end
return function() return function(promise)
Promise = promise
local _M = { local _M = {
currentGenerationIndex = 1, currentGenerationIndex = 1,
currentSpecies = nil, currentSpecies = nil,
finishCallback = nil,
genomeCallback = nil, genomeCallback = nil,
currentGenomeIndex = 1, currentGenomeIndex = 1,
currentFrame = 0, currentFrame = 0,
@ -907,8 +910,6 @@ return function()
upmost = {}, upmost = {},
lastBoth = 0, lastBoth = 0,
runInitialized = {},
onMessageHandler = {}, onMessageHandler = {},
onSaveHandler = {}, onSaveHandler = {},
onLoadHandler = {}, onLoadHandler = {},
@ -933,8 +934,8 @@ return function()
onLoad(_M, handler) onLoad(_M, handler)
end end
_M.run = function(species, generationIdx, genomeCallback, finishCallback) _M.run = function(species, generationIdx, genomeCallback)
run(_M, species, generationIdx, genomeCallback, finishCallback) return run(_M, species, generationIdx, genomeCallback)
end end
return _M return _M

View file

@ -1,6 +1,6 @@
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1").."/.." 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 config = dofile(base.."/config.lua")
local mem = dofile(base.."/mem.lua") local mem = dofile(base.."/mem.lua")

View file

@ -6,7 +6,7 @@ local warn = '========== The ROM must be running before running this script'
io.stderr:write(warn) io.stderr:write(warn)
print(warn) print(warn)
local util = dofile(base.."/util.lua") local util = dofile(base.."/util.lua")()
local mem = dofile(base.."/mem.lua") local mem = dofile(base.."/mem.lua")
local spritelist = dofile(base.."/spritelist.lua") local spritelist = dofile(base.."/spritelist.lua")
local game = dofile(base.."/game.lua") local game = dofile(base.."/game.lua")
@ -436,3 +436,17 @@ input.keyhook("9", true)
input.keyhook("0", true) input.keyhook("0", true)
set_timer_timeout(100 * 1000) 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

View file

@ -2,6 +2,8 @@ local utime = utime
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1") local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local Promise = nil
local _M = {} local _M = {}
_M.isWin = package.config:sub(1, 1) == '\\' _M.isWin = package.config:sub(1, 1) == '\\'
@ -102,27 +104,10 @@ function _M.closeCmd(handle)
end end
end end
function _M.finishWaiting(waiter, count) function _M.waitForFiles(filenames, count, wild)
if count == nil then local promise = Promise.new()
count = 1 promise:resolve()
end
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 type(filenames) == 'string' then
if wild == nil then if wild == nil then
wild = filenames wild = filenames
@ -135,16 +120,15 @@ function _M.startWaiting(filenames, count, wild)
count = #filenames count = #filenames
end end
local poppet = nil
if _M.isWin then if _M.isWin then
local sec, usec = utime() local sec, usec = utime()
print(string.format('Starting watching file at %d', sec * 1000000 + usec)) 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 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") poppet:read("*l")
return poppet
else else
local watchCmd = '' local watchCmd = ''
if count == 1 then if count == 1 then
@ -167,8 +151,24 @@ done ) &
wait wait
EOF]] EOF]]
end end
return _M.popenCmd(watchCmd) poppet = _M.popenCmd(watchCmd)
end 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 end
function _M.table_to_string(tbl) 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 if f~=nil then io.close(f) return true else return false end
end end
return _M return function(promise)
Promise = promise
return _M
end