Added promise library, serpent.load, temp logic

This commit is contained in:
Empathic Qubit 2021-04-28 04:05:16 -04:00
parent 36d945cf47
commit c591641233
7 changed files with 362 additions and 22 deletions

View file

@ -35,7 +35,8 @@ _M.Filename = _M.PoolDir .. _M.State[1]
_M.StartPowerup = 0
_M.NeatConfig = {
Threads = 8,
Threads = 2,
ThreadDontQuit = true,
--Filename = "DP1.state",
SaveFile = _M.Filename .. ".pool",
Filename = _M.Filename,

View file

@ -2,8 +2,6 @@
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local game = dofile(base.."/game.lua")
local config = dofile(base.."/config.lua")
local pool = dofile(base.."/pool.lua")
local util = dofile(base.."/util.lua")

View file

@ -414,13 +414,13 @@ local function loadFile(filename, after)
return
end
local contents = file:read("*all")
local obj, err = loadstring(libDeflate:DecompressDeflate(contents:sub(11, #contents - 8)))
if err ~= nil then
message(string.format("Error parsing: %s", err), 0x00990000)
local ok, obj = serpent.load(libDeflate:DecompressDeflate(contents:sub(11, #contents - 8)))
if not ok then
message("Error parsing pool file", 0x00990000)
return
end
pool = obj()
pool = obj
end
local function savePool()

299
promise.lua Normal file
View file

@ -0,0 +1,299 @@
--[[
The MIT License (MIT)
=====================
Copyright © `2015` `Colin Fein`
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the Software), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]
-- Port of https://github.com/rhysbrettbowen/promise_impl/blob/master/promise.js
-- and https://github.com/rhysbrettbowen/Aplus
--
local queue = {}
local State = {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected',
}
local passthrough = function(x) return x end
local errorthrough = function(x) error(x) end
local function callable_table(callback)
local mt = getmetatable(callback)
return type(mt) == 'table' and type(mt.__call) == 'function'
end
local function is_callable(value)
local t = type(value)
return t == 'function' or (t == 'table' and callable_table(value))
end
local transition, resolve, run
local Promise = {
is_promise = true,
state = State.PENDING
}
Promise.mt = { __index = Promise }
local do_async = function(callback)
if Promise.async then
Promise.async(callback)
else
table.insert(queue, callback)
end
end
local reject = function(promise, reason)
transition(promise, State.REJECTED, reason)
end
local fulfill = function(promise, value)
transition(promise, State.FULFILLED, value)
end
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
promise.state = state
promise.value = value
run(promise)
end
function Promise:next(on_fulfilled, on_rejected)
local promise = Promise.new()
table.insert(self.queue, {
fulfill = is_callable(on_fulfilled) and on_fulfilled or nil,
reject = is_callable(on_rejected) and on_rejected or nil,
promise = promise
})
run(self)
return promise
end
resolve = function(promise, x)
if promise == x then
reject(promise, 'TypeError: cannot resolve a promise with itself')
return
end
local x_type = type(x)
if x_type ~= 'table' then
fulfill(promise, x)
return
end
-- x is a promise in the current implementation
if x.is_promise then
-- 2.3.2.1 if x is pending, resolve or reject this promise after completion
if x.state == State.PENDING then
x:next(
function(value)
resolve(promise, value)
end,
function(reason)
reject(promise, reason)
end
)
return
end
-- if x is not pending, transition promise to x's state and value
transition(promise, x.state, x.value)
return
end
local called = false
-- 2.3.3.1. Catches errors thrown by __index metatable
local success, reason = pcall(function()
local next = x.next
if is_callable(next) then
next(
x,
function(y)
if not called then
resolve(promise, y)
called = true
end
end,
function(r)
if not called then
reject(promise, r)
called = true
end
end
)
else
fulfill(promise, x)
end
end)
if not success then
if not called then
reject(promise, reason)
end
end
end
run = function(promise)
if promise.state == State.PENDING then return end
do_async(function()
-- drain promise.queue while allowing pushes from within callbacks
local q = promise.queue
local i = 0
while i < #q do
i = i + 1
local obj = q[i]
local success, result = pcall(function()
local success = obj.fulfill or passthrough
local failure = obj.reject or errorthrough
local callback = promise.state == State.FULFILLED and success or failure
return callback(promise.value)
end)
if not success then
reject(obj.promise, result)
else
resolve(obj.promise, result)
end
end
for j = 1, i do
q[j] = nil
end
end)
end
function Promise.new(callback)
local instance = {
queue = {}
}
setmetatable(instance, Promise.mt)
if callback then
callback(
function(value)
resolve(instance, value)
end,
function(reason)
reject(instance, reason)
end
)
end
return instance
end
function Promise:catch(callback)
return self:next(nil, callback)
end
function Promise:resolve(value)
fulfill(self, value)
end
function Promise:reject(reason)
reject(self, reason)
end
function Promise.update()
while true do
local async = table.remove(queue, 1)
if not async then
break
end
async()
end
end
-- resolve when all promises complete
function Promise.all(...)
local promises = {...}
local results = {}
local state = State.FULFILLED
local remaining = #promises
local promise = Promise.new()
local check_finished = function()
if remaining > 0 then
return
end
transition(promise, state, results)
end
for i,p in ipairs(promises) do
p:next(
function(value)
results[i] = value
remaining = remaining - 1
check_finished()
end,
function(value)
results[i] = value
remaining = remaining - 1
state = State.REJECTED
check_finished()
end
)
end
check_finished()
return promise
end
-- resolve with first promise to complete
function Promise.race(...)
local promises = {...}
local promise = Promise.new()
Promise.all(...):next(nil, function(value)
reject(promise, value)
end)
local success = function(value)
fulfill(promise, value)
end
for _,p in ipairs(promises) do
p:next(success)
end
return promise
end
return Promise

View file

@ -5,16 +5,14 @@ local serpent = dofile(base.."/serpent.lua")
local util = dofile(base.."/util.lua")
local runnerDataFile = io.open(os.getenv("RUNNER_DATA"), 'r')
local runnerData, err = loadstring(runnerDataFile:read('*a'))
local ok, runnerData = serpent.load(runnerDataFile:read('*a'))
runnerDataFile:close()
if err ~= nil then
print(err)
if not ok then
print("Deserialization error")
return
end
runnerData = runnerData()
local species = runnerData[1]
local speciesId = species.id
@ -110,6 +108,8 @@ runner.run(
)
outFile:write(table.concat(outContents, "\n"))
outFile:close()
exec('quit-emulator')
if os.getenv("RUNNER_DONT_QUIT") == nil then
exec('quit-emulator')
end
end
)

View file

@ -1,8 +1,25 @@
local base = string.gsub(@@LUA_SCRIPT_FILENAME@@, "(.*[/\\])(.*)", "%1")
local util = dofile(base.."/util.lua")
local config = dofile(base.."/config.lua")
local serpent = dofile(base.."/serpent.lua")
local tmpFileName = "/tmp/donk_runner_"..tostring(math.floor(random.integer(0, 0xffffffffffffffff))):hex()
local temps = {
os.getenv("TMPDIR"),
os.getenv("TEMP"),
os.getenv("TEMPDIR"),
os.getenv("TMP"),
}
local tempDir = "/tmp"
for i=1,#temps,1 do
local temp = temps[i]
if temp ~= nil and temp ~= "" then
tempDir = temps[i]
break
end
end
local tmpFileName = tempDir.."donk_runner"
local function message(_M, msg, color)
if color == nil then
@ -70,13 +87,40 @@ return function()
local outputFileName = tmpFileName..'_output_'..i
local inputFileName = tmpFileName.."_input_"..i
print(inputFileName)
local inputFile = io.open(inputFileName, 'w')
inputFile:write(serpent.dump({species[i], generationIdx, outputFileName}))
inputFile:close()
local cmd = "RUNNER_DATA=\""..inputFileName.."\" lsnes \"--rom="..config.ROM.."\" --unpause \"--lua="..base.."/runner-process.lua\""
message(_M, cmd)
local poppet = io.popen(cmd, 'r')
local proc = "lsnes"
if util.isWin then
local checkParent = io.popen('powershell "(Get-WmiObject Win32_Process -Filter ProcessId=$((Get-WmiObject Win32_Process -Filter ProcessId=$((Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId)).ParentProcessId)").ExecutablePath')
proc = checkParent:read("*l")
checkParent:close()
else
-- FIXME Linux
end
print(proc)
local cmd = "\""..proc.."\" \"--rom="..config.ROM.."\" --unpause \"--lua="..base.."/runner-process.lua\""
local envs = {
RUNNER_DATA = inputFileName
}
if config.NeatConfig.ThreadDontQuit then
envs.RUNNER_DONT_QUIT = "1"
end
local cmdParts = {}
for k,v in pairs(envs) do
if util.isWin then
table.insert(cmdParts, string.format("set %s=%s &&", k, v))
else
table.insert(cmdParts, string.format("%s='%s'", k, v))
end
end
table.insert(cmdParts, cmd)
local fullCmd = table.concat(cmdParts, " ")
message(_M, fullCmd)
local poppet = io.popen(fullCmd, 'r')
table.insert(poppets, poppet)
end
@ -91,13 +135,11 @@ return function()
local outputFile = io.open(outputFileName, "r")
local line = ""
repeat
local obj, err = loadstring(line)
if err ~= nil then
local ok, obj = serpent.load(line)
if not ok then
goto continue
end
obj = obj()
if obj == nil then
goto continue
end

View file

@ -557,7 +557,7 @@ local function mainLoop(_M, genome)
_M.genomeCallback(genome, _M.currentGenomeIndex)
end
message(_M, string.format("Gen %d species %d genome %d fitness: %d", _M.currentGenerationIndex, _M.currentSpecies.id, _M.currentGenomeIndex, fitness))
message(_M, string.format("Gen %d species %d genome %d fitness: %d", _M.currentGenerationIndex, _M.currentSpecies.id, _M.currentGenomeIndex, math.floor(fitness)))
_M.currentGenomeIndex = 1
while fitnessAlreadyMeasured(_M) do
_M.currentGenomeIndex = _M.currentGenomeIndex + 1