summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--docs/source/plugins/timer.rst16
-rw-r--r--plugins/autofire/autofire_save.lua8
-rw-r--r--plugins/hiscore/init.lua12
-rw-r--r--plugins/inputmacro/inputmacro_persist.lua6
-rw-r--r--plugins/timecode/init.lua6
-rw-r--r--plugins/timer/init.lua126
-rw-r--r--plugins/timer/plugin.json16
-rw-r--r--plugins/timer/timer_persist.lua249
8 files changed, 325 insertions, 114 deletions
diff --git a/docs/source/plugins/timer.rst b/docs/source/plugins/timer.rst
index 9576f0acc20..578dd2beb50 100644
--- a/docs/source/plugins/timer.rst
+++ b/docs/source/plugins/timer.rst
@@ -1,7 +1,7 @@
.. _plugins-timer:
-Timer Plugin
-============
+Game Play Timer Plugin
+======================
The timer plugin records the total time spent emulating each combination of a
system and a software list item, as well as the number of times each combination
@@ -9,12 +9,12 @@ has been launched. To see the statistics, bring up the main menu (press **Tab**
during emulation by default), choose **Plugin Options**, and then choose
**Timer**.
-Note that this plugin records wall clock time, not emulated time. That is, it
-records the real time duration elapsed while the emulation is running, according
-to the host OS. This may be shorter than the elapsed emulated time if you turn
-off throttling or use MAME’s “fast forward” feature, or it may be longer than
-the elapsed emulated time if you pause the emulation of if the emulation is too
-demanding to run at full speed.
+This plugin records wall clock time (the real time duration elapsed while
+emulation is running, according to the host OS) as well as emulated time. The
+elapsed wall clock time may be shorter than the elapsed emulated time if you
+turn off throttling or use MAME’s “fast forward” feature, or it may be longer
+than the elapsed emulated time if you pause the emulation of if the emulation is
+too demanding to run at full speed.
The statistics are stored in the file **timer.db** in the **timer** folder
inside your plugin data folder (see the
diff --git a/plugins/autofire/autofire_save.lua b/plugins/autofire/autofire_save.lua
index 1ebe06561e1..5ca4cd217b6 100644
--- a/plugins/autofire/autofire_save.lua
+++ b/plugins/autofire/autofire_save.lua
@@ -1,7 +1,7 @@
local lib = {}
local function get_settings_path()
- return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/autofire/'
+ return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/autofire'
end
local function get_settings_filename()
@@ -52,7 +52,7 @@ end
function lib:load_settings()
local buttons = {}
local json = require('json')
- local filename = get_settings_path() .. get_settings_filename()
+ local filename = get_settings_path() .. '/' .. get_settings_filename()
local file = io.open(filename, 'r')
if not file then
return buttons
@@ -81,14 +81,14 @@ function lib:save_settings(buttons)
emu.print_error(string.format('Error saving autofire settings: "%s" is not a directory', path))
return
end
+ local filename = path .. '/' .. get_settings_filename()
if #buttons == 0 then
- os.remove(path .. get_settings_filename())
+ os.remove(filename)
return
end
local json = require('json')
local settings = serialize_settings(buttons)
local data = json.stringify(settings, {indent = true})
- local filename = path .. get_settings_filename()
local file = io.open(filename, 'w')
if not file then
emu.print_error(string.format('Error saving autofire settings: error opening file "%s" for writing', filename))
diff --git a/plugins/hiscore/init.lua b/plugins/hiscore/init.lua
index 4101dd48708..652061c92cf 100644
--- a/plugins/hiscore/init.lua
+++ b/plugins/hiscore/init.lua
@@ -23,7 +23,7 @@ end
function hiscore.startplugin()
local function get_data_path()
- return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/hiscore/'
+ return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/hiscore'
end
-- configuration
@@ -35,7 +35,7 @@ function hiscore.startplugin()
if config_read then
return true
end
- local filename = get_data_path() .. 'plugin.cfg'
+ local filename = get_data_path() .. '/plugin.cfg'
local file = io.open(filename, 'r')
if file then
local json = require('json')
@@ -67,7 +67,7 @@ function hiscore.startplugin()
end
local settings = { only_save_at_exit = not timed_save }
-- TODO: other settings?
- local filename = path .. 'plugin.cfg'
+ local filename = path .. '/plugin.cfg'
local json = require('json')
local data = json.stringify(settings, { indent = true })
local file = io.open(filename, 'w')
@@ -210,9 +210,9 @@ function hiscore.startplugin()
local r;
if emu.softname() ~= "" then
local soft = emu.softname():match("([^:]*)$")
- r = get_data_path() .. emu.romname() .. "_" .. soft .. ".hi";
+ r = get_data_path() .. '/' .. emu.romname() .. "_" .. soft .. ".hi";
else
- r = get_data_path() .. emu.romname() .. ".hi";
+ r = get_data_path() .. '/' .. emu.romname() .. ".hi";
end
return r;
end
@@ -223,7 +223,7 @@ function hiscore.startplugin()
local output = io.open(get_file_name(), "wb");
if not output then
-- attempt to create the directory, and try again
- lfs.mkdir( get_data_path() );
+ lfs.mkdir(get_data_path());
output = io.open(get_file_name(), "wb");
end
emu.print_verbose("hiscore: write_scores output")
diff --git a/plugins/inputmacro/inputmacro_persist.lua b/plugins/inputmacro/inputmacro_persist.lua
index 4e043852c51..6c2aa6fd188 100644
--- a/plugins/inputmacro/inputmacro_persist.lua
+++ b/plugins/inputmacro/inputmacro_persist.lua
@@ -5,7 +5,7 @@
-- Helpers
local function settings_path()
- return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/inputmacro/'
+ return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/inputmacro'
end
local function settings_filename()
@@ -101,7 +101,7 @@ end
local lib = { }
function lib:load_settings()
- filename = settings_path() .. settings_filename()
+ filename = settings_path() .. '/' .. settings_filename()
local file = io.open(filename, 'r')
if not file then
return { }
@@ -133,7 +133,7 @@ function lib:save_settings(macros)
emu.print_error(string.format('Error saving input macros: "%s" is not a directory', path))
return
end
- filename = path .. settings_filename()
+ filename = path .. '/' .. settings_filename()
if #macros == 0 then
os.remove(filename)
diff --git a/plugins/timecode/init.lua b/plugins/timecode/init.lua
index 084d3b3e8d6..09e49229cae 100644
--- a/plugins/timecode/init.lua
+++ b/plugins/timecode/init.lua
@@ -34,7 +34,7 @@ function timecode.startplugin()
local function get_settings_path()
- return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/timecode/'
+ return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/timecode'
end
@@ -50,7 +50,7 @@ function timecode.startplugin()
set_default_hotkey()
-- try to open configuration file
- local cfgname = get_settings_path() .. 'plugin.cfg'
+ local cfgname = get_settings_path() .. '/plugin.cfg'
local cfgfile = io.open(cfgname, 'r')
if not cfgfile then
return -- probably harmless, configuration just doesn't exist yet
@@ -97,7 +97,7 @@ function timecode.startplugin()
settings.hotkey = hotkey_cfg
end
local data = json.stringify(settings, { indent = true })
- local cfgname = path .. 'plugin.cfg'
+ local cfgname = path .. '/plugin.cfg'
local cfgfile = io.open(cfgname, 'w')
if not cfgfile then
emu.print_error(string.format('Error saving timecode recorder settings: error opening file "%s" for writing', cfgname))
diff --git a/plugins/timer/init.lua b/plugins/timer/init.lua
index 19755f7a348..d39243da730 100644
--- a/plugins/timer/init.lua
+++ b/plugins/timer/init.lua
@@ -1,111 +1,73 @@
-- license:BSD-3-Clause
--- copyright-holders:Carl
-require('lfs')
-local sqlite3 = require('lsqlite3')
-local exports = {}
-exports.name = "timer"
-exports.version = "0.0.2"
-exports.description = "Game play timer"
-exports.license = "BSD-3-Clause"
-exports.author = { name = "Carl" }
+-- copyright-holders:Vas Crabb
+local exports = {
+ name = 'timer',
+ version = '0.0.3',
+ description = 'Game play timer',
+ license = 'BSD-3-Clause',
+ author = { name = 'Vas Crabb' } }
local timer = exports
function timer.startplugin()
- local dir = emu.subst_env(manager.options.entries.homepath:value())
- local timer_db = dir .. "/timer/timer.db"
- local timer_started = false
local total_time = 0
local start_time = 0
local play_count = 0
+ local emu_total = emu.attotime()
- local function save()
- total_time = total_time + (os.time() - start_time)
-
- local db = assert(sqlite3.open(timer_db))
-
- local insert_stmt = assert( db:prepare("INSERT OR IGNORE INTO timer VALUES (?, ?, 0, 0)") )
- insert_stmt:bind_values(emu.romname(), emu.softname())
- insert_stmt:step()
- insert_stmt:reset()
-
- local update_stmt = assert( db:prepare("UPDATE timer SET total_time=?, play_count=? WHERE driver=? AND software=?") )
- update_stmt:bind_values(total_time, play_count,emu.romname(), emu.softname())
- update_stmt:step()
- update_stmt:reset()
-
- assert(db:close() == sqlite3.OK)
- end
-
-
- emu.register_start(function()
- local file
- if timer_started then
- save()
- end
- timer_started = true
- lfs.mkdir(dir .. '/timer')
- local db = assert(sqlite3.open(timer_db))
- local found=false
- db:exec([[select * from sqlite_master where name='timer';]], function(...) found=true return 0 end)
- if not found then
- db:exec[[ CREATE TABLE timer (
- driver VARCHAR(32) PRIMARY KEY,
- software VARCHAR(40),
- total_time INTEGER NOT NULL,
- play_count INTEGER NOT NULL
- ); ]]
- end
-
- local stmt, row
- stmt = db:prepare("SELECT total_time, play_count FROM timer WHERE driver = ? AND software = ?")
- stmt:bind_values(emu.romname(), emu.softname())
- if (stmt:step() == sqlite3.ROW) then
- row = stmt:get_named_values()
- play_count = row.play_count
- total_time = row.total_time
- else
- play_count = 0
- total_time = 0
- end
-
- assert(db:close() == sqlite3.OK)
-
- start_time = os.time()
- play_count = play_count + 1
- end)
+ local reference = 0
+ local lastupdate
- emu.register_stop(function()
- timer_started = false
- save()
- total_time = 0
- play_count = 0
- end)
local function sectohms(time)
- local hrs = math.floor(time / 3600)
- local min = math.floor((time % 3600) / 60)
+ local hrs = time // 3600
+ local min = (time % 3600) // 60
local sec = time % 60
- return string.format(_p("plugin-timer", "%03d:%02d:%02d"), hrs, min, sec)
+ return string.format(_p('plugin-timer', '%03d:%02d:%02d'), hrs, min, sec)
end
- local lastupdate
-
local function menu_populate()
lastupdate = os.time()
- local time = lastupdate - start_time
+ local refname = (reference == 0) and _p('plugin-timer', 'Wall clock') or _p('plugin-timer', 'Emulated time')
+ local time = (reference == 0) and (lastupdate - start_time) or manager.machine.time.seconds
+ local total = (reference == 0) and (total_time + time) or (manager.machine.time + emu_total).seconds
return
- {{ _p("plugin-timer", "Current time"), sectohms(time), "off" },
- { _p("plugin-timer", "Total time"), sectohms(total_time + time), "off" },
- { _p("plugin-timer", "Play Count"), play_count, "off" }},
+ {
+ { _p("plugin-timer", "Reference"), refname, (reference == 0) and 'r' or 'l' },
+ { '---', '', '' },
+ { _p("plugin-timer", "Current time"), sectohms(time), "off" },
+ { _p("plugin-timer", "Total time"), sectohms(total), "off" },
+ { _p("plugin-timer", "Play Count"), play_count, "off" } },
nil,
"idle"
end
local function menu_callback(index, event)
+ if (index == 1) and ((event == 'left') or (event == 'right') or (event == 'select')) then
+ reference = reference ~ 1
+ return true
+ end
return os.time() > lastupdate
end
+
+ emu.register_start(
+ function()
+ if emu.romname() ~= '___empty' then
+ start_time = os.time()
+ local persister = require('timer/timer_persist')
+ total_time, play_count, emu_total = persister:start_session()
+ end
+ end)
+
+ emu.register_stop(
+ function()
+ if emu.romname() ~= '___empty' then
+ local persister = require('timer/timer_persist')
+ persister:update_totals(start_time)
+ end
+ end)
+
emu.register_menu(menu_callback, menu_populate, _p("plugin-timer", "Timer"))
end
diff --git a/plugins/timer/plugin.json b/plugins/timer/plugin.json
index 005a0fad9a4..6755f434f94 100644
--- a/plugins/timer/plugin.json
+++ b/plugins/timer/plugin.json
@@ -1,10 +1,10 @@
{
- "plugin": {
- "name": "timer",
- "description": "Timer plugin",
- "version": "0.0.1",
- "author": "Carl",
- "type": "plugin",
- "start": "false"
- }
+ "plugin": {
+ "name": "timer",
+ "description": "Game play timer",
+ "version": "0.0.3",
+ "author": "Vas Crabb",
+ "type": "plugin",
+ "start": "false"
+ }
}
diff --git a/plugins/timer/timer_persist.lua b/plugins/timer/timer_persist.lua
new file mode 100644
index 00000000000..5ed33f03eb4
--- /dev/null
+++ b/plugins/timer/timer_persist.lua
@@ -0,0 +1,249 @@
+-- license:BSD-3-Clause
+-- copyright-holders:Vas Crabb
+
+local sqlite3 = require('lsqlite3')
+
+
+local function check_schema(db)
+ local create_statement =
+ [[CREATE TABLE timer (
+ driver VARCHAR(32) NOT NULL,
+ softlist VARCHAR(24) NOT NULL DEFAULT '',
+ software VARCHAR(16) NOT NULL DEFAULT '',
+ total_time INTEGER NOT NULL DEFAULT 0,
+ play_count INTEGER NOT NULL DEFAULT 0,
+ emu_sec INTEGER NOT NULL DEFAULT 0,
+ emu_nsec INTEGER NOT NULL DEFAULT 0,
+ PRIMARY KEY (driver, softlist, software));]]
+
+ -- create table if it doesn't exist yet
+ local table_found = false
+ db:exec(
+ [[SELECT * FROM sqlite_master WHERE type = 'table' AND name='timer';]],
+ function()
+ table_found = true
+ end)
+ if not table_found then
+ emu.print_verbose('Creating timer database table')
+ db:exec(create_statement)
+ return
+ end
+
+ -- check recently added columns
+ local have_softlist = false
+ local have_emu_sec = false
+ local have_emu_nsec = false
+ db:exec(
+ [[PRAGMA table_info(timer);]],
+ function(udata, n, vals, cols)
+ for i, name in ipairs(cols) do
+ if name == 'name' then
+ if vals[i] == 'softlist' then
+ have_softlist = true
+ elseif vals[i] == 'emu_sec' then
+ have_emu_sec = true
+ elseif vals[i] == 'emu_nsec' then
+ have_emu_nsec = true
+ end
+ return 0
+ end
+ end
+ return 0
+ end)
+ if not have_softlist then
+ emu.print_verbose('Adding softlist column to timer database')
+ db:exec([[ALTER TABLE timer ADD COLUMN softlist VARCHAR(24) NOT NULL DEFAULT '';]])
+ local to_split = { }
+ db:exec(
+ [[SELECT DISTINCT software FROM timer WHERE software LIKE '%:%';]],
+ function(udata, n, vals)
+ table.insert(to_split, vals[1])
+ return 0
+ end)
+ if #to_split > 0 then
+ local update = db:prepare([[UPDATE timer SET softlist = ?, software = ? WHERE software = ?;]])
+ for i, softname in ipairs(to_split) do
+ local x, y = softname:find(':')
+ local softlist = softname:sub(1, x - 1)
+ local software = softname:sub(y + 1)
+ update:bind_values(softlist, software, softname)
+ update:step()
+ update:reset()
+ end
+ end
+ end
+ if not have_emu_sec then
+ emu.print_verbose('Adding emu_sec column to timer database')
+ db:exec([[ALTER TABLE timer ADD COLUMN emu_sec INTEGER NOT NULL DEFAULT 0;]])
+ end
+ if not have_emu_nsec then
+ emu.print_verbose('Adding emu_nsec column to timer database')
+ db:exec([[ALTER TABLE timer ADD COLUMN emu_nsec INTEGER NOT NULL DEFAULT 0;]])
+ end
+
+ -- check the required columns are in the primary key
+ local index_name
+ db:exec(
+ [[SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'timer';]],
+ function(udata, n, vals)
+ index_name = vals[1]
+ end)
+ local index_good
+ if index_name then
+ local driver_indexed = false
+ local softlist_indexed = false
+ local software_indexed = false
+ db:exec(
+ string.format([[PRAGMA index_info('%s');]], index_name), -- can't use prepared statement for PRAGMA
+ function(udata, n, vals, cols)
+ for i, name in ipairs(cols) do
+ if name == 'name' then
+ if vals[i] == 'driver' then
+ driver_indexed = true
+ elseif vals[i] == 'softlist' then
+ softlist_indexed = true
+ elseif vals[i] == 'software' then
+ software_indexed = true
+ end
+ return 0
+ end
+ end
+ return 0
+ end)
+ index_good = driver_indexed and softlist_indexed and software_indexed
+ end
+
+ -- if the required columns are not indexed, migrate to a new table with desired primary key
+ if not index_good then
+ emu.print_verbose('Re-indexing timer database table')
+ db:exec([[DROP TABLE IF EXISTS timer_old;]])
+ db:exec([[ALTER TABLE timer RENAME TO timer_old;]])
+ db:exec(create_statement)
+ db:exec(
+ [[INSERT
+ INTO timer (driver, softlist, software, total_time, play_count, emu_sec, emu_nsec)
+ SELECT driver, softlist, software, total_time, play_count, emu_sec, emu_nsec FROM timer_old;]])
+ db:exec([[DROP TABLE timer_old;]])
+ end
+end
+
+
+local function open_database()
+ -- make sure settings directory exists
+ local dir = emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/timer'
+ local attr = lfs.attributes(dir)
+ if not attr then
+ lfs.mkdir(dir)
+ elseif attr.mode ~= 'directory' then
+ emu.print_error(string.format('Error opening timer database: "%s" is not a directory', dir))
+ return nil
+ end
+
+ -- open database
+ local filename = dir .. '/timer.db'
+ local db = sqlite3.open(filename)
+ if not db then
+ emu.print_error(string.format('Error opening timer database file "%s"', filename))
+ return nil
+ end
+
+ -- make sure schema is up-to-date
+ check_schema(db)
+ return db
+end
+
+
+local function get_software()
+ local softname = emu.softname()
+ local i, j = softname:find(':')
+ if i then
+ return softname:sub(1, i - 1), softname:sub(j + 1)
+ else
+ -- FIXME: need a way to get the implicit software list when no colon in the option value
+ return '', softname
+ end
+end
+
+
+local function get_current(db)
+ local statement = db:prepare(
+ [[SELECT
+ total_time, play_count, emu_sec, emu_nsec
+ FROM timer
+ WHERE driver = ? AND softlist = ? AND software = ?;]])
+ statement:bind_values(emu.romname(), get_software())
+ local result
+ if statement:step() == sqlite3.ROW then
+ result = statement:get_named_values()
+ end
+ statement:reset()
+ return result
+end
+
+
+local lib = { }
+
+function lib:start_session()
+ -- open database
+ local db = open_database()
+ if not db then
+ return 0, 0, emu.attotime()
+ end
+
+ -- get existing values
+ local row = get_current(db)
+ local update
+ if row then
+ update = db:prepare(
+ [[UPDATE timer
+ SET play_count = play_count + 1
+ WHERE driver = ? AND softlist = ? AND software = ?;]])
+ else
+ row = { total_time = 0, play_count = 0, emu_sec = 0, emu_nsec = 0 }
+ update = db:prepare(
+ [[INSERT
+ INTO timer (driver, softlist, software, total_time, play_count, emu_sec, emu_nsec)
+ VALUES (?, ?, ?, 0, 1, 0, 0);]])
+ end
+ update:bind_values(emu.romname(), get_software())
+ update:step()
+ update:reset()
+ return row.total_time, row.play_count + 1, emu.attotime.from_seconds(row.emu_sec) + emu.attotime.from_nsec(row.emu_nsec)
+end
+
+function lib:update_totals(start)
+ -- open database
+ local db = open_database()
+ if not db then
+ return
+ end
+
+ -- get existing values
+ local row = get_current(db)
+ if not row then
+ row = { total_time = 0, play_count = 1, emu_sec = 0, emu_nsec = 0 }
+ end
+
+ -- calculate new totals
+ local emu_total = emu.attotime.from_seconds(row.emu_sec) + emu.attotime.from_nsec(row.emu_nsec) + manager.machine.time
+ row.total_time = os.time() - start + row.total_time
+ row.emu_sec = emu_total.seconds
+ row.emu_nsec = emu_total.nsec
+
+ -- update database
+ local update = db:prepare(
+ [[INSERT OR REPLACE
+ INTO timer (driver, softlist, software, total_time, play_count, emu_sec, emu_nsec)
+ VALUES (?, ?, ?, ?, ?, ?, ?);]])
+ local softlist, software = get_software()
+ update:bind_values(emu.romname(), softlist, software, row.total_time, row.play_count, row.emu_sec, row.emu_nsec)
+ update:step()
+ update:reset()
+
+ -- close database
+ if db:close() ~= sqlite3.OK then
+ emu.print_error('Error closing timer database')
+ end
+end
+
+return lib