summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Aaron Giles <aaron@aarongiles.com>2021-04-20 02:12:47 -0700
committer Aaron Giles <aaron@aarongiles.com>2021-04-20 02:12:47 -0700
commit8d047a8f0dcfb1bdaa4dcf87c001a30ebcc7cca8 (patch)
treef0ca7134fd27aa3d9967195dd8946be542c749cc
parent7e33cb29e24b2ce9c4c3bf7b1d7b8784a93f3172 (diff)
Better handling of null/missing items. More consistent error handling. Reduced compression to default to speed up saves. Optimized simple binary saves as well. Device interfaces now have automatic containers. Fixed duplicate entry detection. Added logic to parse an item we're skipping. Added detection of missing/duplicate items from input JSON. Fixed timing for save/load.save-experiments
-rw-r--r--src/emu/debug/dvsave.cpp10
-rw-r--r--src/emu/device.cpp6
-rw-r--r--src/emu/diexec.cpp3
-rw-r--r--src/emu/dinetwork.cpp3
-rw-r--r--src/emu/dipalette.cpp6
-rw-r--r--src/emu/dirom.ipp3
-rw-r--r--src/emu/diserial.cpp3
-rw-r--r--src/emu/diserial.h3
-rw-r--r--src/emu/divtlb.cpp6
-rw-r--r--src/emu/machine.cpp6
-rw-r--r--src/emu/save.cpp469
-rw-r--r--src/emu/save.h28
12 files changed, 386 insertions, 160 deletions
diff --git a/src/emu/debug/dvsave.cpp b/src/emu/debug/dvsave.cpp
index eda3fb59235..141d744f7e9 100644
--- a/src/emu/debug/dvsave.cpp
+++ b/src/emu/debug/dvsave.cpp
@@ -63,6 +63,11 @@ void debug_view_save::build_list_recursive(save_registered_item &item, uintptr_t
if (item.unwrap_and_update_base(objbase))
return build_list_recursive(item.subitems().front(), objbase, depth, count);
+ // only containers are allowed to have null bases past this point; treat
+ // everything else as non-existent
+ if (objbase == 0 && item.type() != save_registered_item::TYPE_CONTAINER)
+ return;
+
// switch off the type
switch (item.type())
{
@@ -305,6 +310,11 @@ std::string debug_view_save::save_item::value(save_registered_item &item, int co
if (item.unwrap_and_update_base(objbase))
return value(item.subitems().front(), 0, objbase, collapsed);
+ // only containers are allowed to have null bases past this point; treat
+ // everything else as non-existent
+ if (objbase == 0 && item.type() != save_registered_item::TYPE_CONTAINER)
+ return "(null)";
+
char tempbuf[256];
tempbuf[0] = 0;
int pos = 0;
diff --git a/src/emu/device.cpp b/src/emu/device.cpp
index 4f61cd405a2..c7c9c4edb57 100644
--- a/src/emu/device.cpp
+++ b/src/emu/device.cpp
@@ -694,8 +694,12 @@ void device_t::register_save(save_registrar &save)
.reg(NAME(m_clock_scale));
// let the interfaces save their states
+ save_registrar intf_container(save, "interfaces");
for (device_interface &intf : interfaces())
- intf.interface_register_save(save);
+ {
+ save_registrar container(intf_container, intf.interface_type());
+ intf.interface_register_save(container);
+ }
// then the device itself
int state_registrations = machine().save().binary_size();
diff --git a/src/emu/diexec.cpp b/src/emu/diexec.cpp
index 799626fad3e..936f7f8602b 100644
--- a/src/emu/diexec.cpp
+++ b/src/emu/diexec.cpp
@@ -501,8 +501,7 @@ void device_execute_interface::interface_clock_changed()
void device_execute_interface::interface_register_save(save_registrar &save)
{
// put execute items in their own container
- save_registrar(save, "execute")
- .reg(NAME(m_suspend))
+ save.reg(NAME(m_suspend))
.reg(NAME(m_nextsuspend))
.reg(NAME(m_eatcycles))
.reg(NAME(m_nexteatcycles))
diff --git a/src/emu/dinetwork.cpp b/src/emu/dinetwork.cpp
index c9a81908b78..f152bfd1561 100644
--- a/src/emu/dinetwork.cpp
+++ b/src/emu/dinetwork.cpp
@@ -25,8 +25,7 @@ void device_network_interface::interface_pre_start()
void device_network_interface::interface_register_save(save_registrar &save)
{
- save_registrar(save, "network")
- .reg(NAME(m_loopback_control));
+ save.reg(NAME(m_loopback_control));
}
int device_network_interface::send(u8 *buf, int len)
diff --git a/src/emu/dipalette.cpp b/src/emu/dipalette.cpp
index d75a5ec598f..24316e9f2cf 100644
--- a/src/emu/dipalette.cpp
+++ b/src/emu/dipalette.cpp
@@ -102,14 +102,12 @@ void device_palette_interface::interface_post_start()
void device_palette_interface::interface_register_save(save_registrar &save)
{
- save_registrar container(save, "palette");
-
// save indirection tables if we have them
- container.reg(NAME(m_save_pen))
+ save.reg(NAME(m_save_pen))
.reg(NAME(m_save_contrast));
if (m_indirect_colors.size() > 0)
- container.reg(NAME(m_indirect_colors))
+ save.reg(NAME(m_indirect_colors))
.reg(NAME(m_indirect_pens));
}
diff --git a/src/emu/dirom.ipp b/src/emu/dirom.ipp
index f6460e84d33..b734951d8c8 100644
--- a/src/emu/dirom.ipp
+++ b/src/emu/dirom.ipp
@@ -136,8 +136,7 @@ void device_rom_interface<AddrWidth, DataWidth, AddrShift, Endian>::interface_re
{
device_memory_interface::interface_register_save(save);
- save_registrar(save, "rom")
- .reg(NAME(m_cur_bank))
+ save.reg(NAME(m_cur_bank))
.reg(NAME(m_bank_count));
}
diff --git a/src/emu/diserial.cpp b/src/emu/diserial.cpp
index a4731150862..421d6f0e040 100644
--- a/src/emu/diserial.cpp
+++ b/src/emu/diserial.cpp
@@ -77,8 +77,7 @@ void device_serial_interface::interface_pre_start()
void device_serial_interface::interface_register_save(save_registrar &save)
{
- save_registrar(save, "serial")
- .reg(NAME(m_df_start_bit_count))
+ save.reg(NAME(m_df_start_bit_count))
.reg(NAME(m_df_word_length))
.reg(NAME(m_df_parity))
.reg(NAME(m_df_stop_bit_count))
diff --git a/src/emu/diserial.h b/src/emu/diserial.h
index b21eb064d03..e6fca51a8d0 100644
--- a/src/emu/diserial.h
+++ b/src/emu/diserial.h
@@ -192,8 +192,7 @@ protected:
{
device_serial_interface::interface_register_save(save);
- save_registrar(save, "buffered_serial")
- .reg(NAME(m_fifo))
+ save.reg(NAME(m_fifo))
.reg(NAME(m_head))
.reg(NAME(m_tail))
.reg(NAME(m_empty));
diff --git a/src/emu/divtlb.cpp b/src/emu/divtlb.cpp
index df880676927..0fef07a8bb9 100644
--- a/src/emu/divtlb.cpp
+++ b/src/emu/divtlb.cpp
@@ -114,13 +114,11 @@ void device_vtlb_interface::interface_pre_start()
void device_vtlb_interface::interface_register_save(save_registrar &save)
{
- save_registrar container(save, "vtlb");
-
- container.reg(NAME(m_live))
+ save.reg(NAME(m_live))
.reg(NAME(m_table))
.reg(NAME(m_refcnt));
if (m_fixed > 0)
- container.reg(NAME(m_fixedpages));
+ save.reg(NAME(m_fixedpages));
}
diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp
index 7e7952f935e..e2ba0f6cf70 100644
--- a/src/emu/machine.cpp
+++ b/src/emu/machine.cpp
@@ -263,7 +263,7 @@ void running_machine::start()
save().register_presave(save_prepost_delegate(FUNC(running_machine::presave_all_devices), this));
start_all_devices();
for (device_t &device : device_enumerator(root_device()))
- m_save.root_registrar().reg(device, device.tag(), device.type().size());
+ m_save.root_registrar().reg(device, string_format("device%s", device.tag()).c_str(), device.type().size());
save().register_postload(save_prepost_delegate(FUNC(running_machine::postload_all_devices), this));
// save outputs created before start time
@@ -938,12 +938,12 @@ void running_machine::handle_saveload()
auto const filerr = file.open(m_saveload_pending_file);
if (filerr == osd_file::error::NONE)
{
- osd_ticks_t start = osd_ticks();
const char *const opnamed = (m_saveload_schedule == saveload_schedule::LOAD) ? "loaded" : "saved";
- osd_ticks_t end = osd_ticks();
// read/write the save state
+ osd_ticks_t start = osd_ticks();
save_error saverr = (m_saveload_schedule == saveload_schedule::LOAD) ? m_save.load_file(file) : m_save.save_file(file);
+ osd_ticks_t end = osd_ticks();
// handle the result
switch (saverr)
diff --git a/src/emu/save.cpp b/src/emu/save.cpp
index 04204d62699..0c7a75b65dc 100644
--- a/src/emu/save.cpp
+++ b/src/emu/save.cpp
@@ -39,6 +39,8 @@
#define LOG(x) do { if (VERBOSE) machine().logerror x; } while (0)
+#define DUMP_INITIAL_JSON_SAVE (0)
+
//**************************************************************************
@@ -109,7 +111,7 @@ bool zlib_write_streamer::begin()
// initialize the zlib engine; the negative window size means
// no headers, which is what a .ZIP file wants
- return (deflateInit2(&m_stream, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) == Z_OK);
+ return (deflateInit2(&m_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) == Z_OK);
}
@@ -215,10 +217,13 @@ public:
// simple getters
char const *json_string() { m_json[m_json_offset] = 0; return &m_json[0]; }
- int json_length() const { return m_json_offset; }
+ u32 json_length() const { return m_json_offset; }
+
+ // setters
+ void json_set_length(u32 length) { m_json_offset = length; }
// append a character to the JSON stream
- save_zip_state &json_append(char ch) { m_json[m_json_offset++] = ch; return *this; }
+ save_zip_state &json_append(char ch) { m_json[m_json_offset++] = ch; return *this; }
// append an end-of-line sequence to the JSON stream
save_zip_state &json_append_eol() { return json_append(13).json_append(10); }
@@ -915,6 +920,7 @@ public:
bool json_parse_int_string(s64 &result);
bool json_parse_uint_string(u64 &result);
bool json_parse_string(std::string &result);
+ void json_parse_remaining_value(save_registered_item &item);
// initialize, checking the input file basic validity
save_error init();
@@ -927,18 +933,24 @@ public:
// report a warning
template<typename... Params>
- void report_warning(char const *format, Params &&... args)
+ void report_warning(save_registered_item &item, char const *format, Params &&... args)
+ {
+ m_warnings.append(item.full_name()).append(": ").append(string_format(format, std::forward<Params>(args)...)).append("\n");
+ }
+
+ // report a warning and consume the remaining value
+ template<typename... Params>
+ void report_warning_and_consume(save_registered_item &item, char const *format, Params &&... args)
{
- m_warnings.append(string_format(format, std::forward<Params>(args)...));
- m_warnings += "\n";
+ m_warnings.append(item.full_name()).append(": ").append(string_format(format, std::forward<Params>(args)...)).append("\n");
+ json_parse_remaining_value(item);
}
// report an error; this implicitly throws to exit
template<typename... Params>
- void report_error(save_error type, char const *format, Params &&... args)
+ void report_error(save_registered_item &item, save_error type, char const *format, Params &&... args)
{
- m_errors.append(string_format(format, std::forward<Params>(args)...));
- m_errors += "\n";
+ m_errors.append(item.full_name()).append(": ").append(string_format(format, std::forward<Params>(args)...)).append("\n");
throw load_error(type);
}
@@ -1106,6 +1118,67 @@ bool load_zip_state::json_parse_string(std::string &result)
//-------------------------------------------------
+// json_parse_remaining_value - parse and throw
+// away any remaining value data
+//-------------------------------------------------
+
+void load_zip_state::json_parse_remaining_value(save_registered_item &item)
+{
+ // value ends at a comma or end of struct/array
+ while (json_peek() != 0 && json_peek() != ',' && json_peek() != ']' && json_peek() != '}')
+ {
+ // skip any leading whitespace
+ if (json_matches('['))
+ {
+ // if a start of array marker, parse until done
+ while (1)
+ {
+ json_parse_remaining_value(item);
+ if (json_matches(']'))
+ break;
+ if (!json_matches(','))
+ report_error(item, STATERR_MALFORMED_JSON, "Expected ','");
+ }
+ }
+ else if (json_matches('{'))
+ {
+ // if a start of struct market, parse until done
+ while (1)
+ {
+ std::string name;
+ if (!json_parse_string(name) || name == "")
+ report_error(item, STATERR_MALFORMED_JSON, "Expected name within struct");
+ if (!json_matches(':'))
+ report_error(item, STATERR_MALFORMED_JSON, "Expected ':'");
+ json_parse_remaining_value(item);
+ if (json_matches('}'))
+ break;
+ if (!json_matches(','))
+ report_error(item, STATERR_MALFORMED_JSON, "Expected ','");
+ }
+ }
+ else if (json_peek() == '"')
+ {
+ // if a string, consume it
+ std::string dummy;
+ json_parse_string(dummy);
+ }
+ else if (isdigit(json_peek()) || json_peek() == '-' || json_peek() == '+')
+ {
+ // if a number, consume it
+ double dummy;
+ json_parse_number(dummy);
+ }
+ else
+ {
+ // otherwise, just advance until we see something interesting
+ json_set_position(json_position() + 1);
+ }
+ }
+}
+
+
+//-------------------------------------------------
// init - initialize by parsing the ZIP structure
// and loading the JSON data
//-------------------------------------------------
@@ -1290,11 +1363,6 @@ save_registered_item::save_registered_item(save_registered_item &parent, uintptr
m_native_size(native_size),
m_name(name)
{
- // cleanup names a bit
- if (m_name[0] == '*')
- m_name.erase(0, 1);
- if (m_name[0] == 'm' && m_name[1] == '_')
- m_name.erase(0, 2);
}
@@ -1311,17 +1379,21 @@ std::string save_registered_item::full_name() const
// get the parent's full name
std::string result = m_parent->full_name();
+ // if we're a unique or vector, just pass it through without adding more
+ if (type() == TYPE_UNIQUE || type() == TYPE_VECTOR)
+ return result;
+
+ // if we're an array item, append an array indicator
+ if (is_array())
+ return result.append("[]");
+
// maybe add a separator
char end = result[result.length() - 1];
if (end != ':' && end != '.' && result.length() != 0)
- result += ".";
+ result.append(".");
// then append our bit
- if (m_name.length() == 0 && m_parent->is_array())
- result += "[]";
- else
- result += m_name;
- return result;
+ return result.append(m_name);
}
@@ -1331,11 +1403,17 @@ std::string save_registered_item::full_name() const
save_registered_item &save_registered_item::append(uintptr_t ptr_offset, save_type type, u32 native_size, char const *name, u32 count)
{
+ // cleanup names a bit
+ if (name[0] == '*')
+ name++;
+ if (name[0] == 'm' && name[1] == '_')
+ name += 2;
+
// make sure there are no duplicates
if (find(name) != nullptr)
- throw emu_fatalerror("Duplicate save state registration '%s'\n", name);
+ throw emu_fatalerror("%s: Duplicate save state registration '%s'\n", full_name().c_str(), name);
-//printf("%s '%s': adding %s '%s' @ %llX, size %d\n", type_string(m_type, m_native_size).c_str(), m_name.c_str(), type_string(type, native_size).c_str(), name, ptr_offset, native_size);
+printf("%s: adding %s '%s' @ %I64X, size %d\n", full_name().c_str(), type_string().c_str(), name, ptr_offset, native_size);
// add the item to the back of the list
m_items.emplace_back(*this, ptr_offset, type, native_size, name, count);
@@ -1347,19 +1425,29 @@ save_registered_item &save_registered_item::append(uintptr_t ptr_offset, save_ty
// find - find a subitem by name
//-------------------------------------------------
-save_registered_item *save_registered_item::find(char const *name)
+save_registered_item *save_registered_item::find(char const *name, u32 &index)
{
// blank names can't be found this way
if (name[0] == 0)
return nullptr;
// make sure there are no duplicates
+ index = 0;
for (auto &item : m_items)
+ {
if (strcmp(item.name(), name) == 0)
return &item;
+ index++;
+ }
return nullptr;
}
+save_registered_item *save_registered_item::find(char const *name)
+{
+ u32 dummy;
+ return find(name, dummy);
+}
+
//-------------------------------------------------
// is_replicatable - can this item be replicated
@@ -1407,25 +1495,26 @@ bool save_registered_item::is_replicatable(bool parent_is_array) const
bool save_registered_item::sort_and_prune()
{
- // only applies to arrays, structs, and containers; don't prune anything else
- if (!is_array() && !is_struct_or_container())
- return false;
-
- // first prune any empty items
- for (auto it = m_items.begin(); it != m_items.end(); )
+ // structs and containers prune empty items and sort them
+ if (is_struct_or_container())
{
- if (it->sort_and_prune())
- it = m_items.erase(it);
- else
- ++it;
- }
+ // first prune any empty items
+ for (auto it = m_items.begin(); it != m_items.end(); )
+ {
+ if (it->sort_and_prune())
+ it = m_items.erase(it);
+ else
+ ++it;
+ }
- // then sort the rest if we have more than 1
- if (is_struct_or_container() && m_items.size() > 1)
- m_items.sort([] (auto const &x, auto const &y) { return (std::strcmp(x.name(), y.name()) < 0); });
+ // then sort the rest if we have more than 1
+ if (m_items.size() > 1)
+ m_items.sort([] (auto const &x, auto const &y) { return (std::strcmp(x.name(), y.name()) < 0); });
- // return true if we have nothing
- return (m_items.size() == 0);
+ // prune if nothing is left
+ return (m_items.size() == 0);
+ }
+ return false;
}
@@ -1475,6 +1564,11 @@ u64 save_registered_item::save_binary(u8 *ptr, u64 length, uintptr_t objbase) co
if (unwrap_and_update_base(objbase))
return m_items.front().save_binary(ptr, length, objbase);
+ // only containers are allowed to have null bases past this point; treat
+ // everything else as non-existent
+ if (objbase == 0 && type() != TYPE_CONTAINER)
+ return 0;
+
// switch off the type
u64 offset = 0;
switch (type())
@@ -1496,8 +1590,8 @@ u64 save_registered_item::save_binary(u8 *ptr, u64 length, uintptr_t objbase) co
break;
// structs and containers iterate over owned items
- case TYPE_CONTAINER:
case TYPE_STRUCT:
+ case TYPE_CONTAINER:
for (auto &item : m_items)
offset += item.save_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase);
break;
@@ -1507,13 +1601,27 @@ u64 save_registered_item::save_binary(u8 *ptr, u64 length, uintptr_t objbase) co
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
{
+ // nothing to do if count is 0
+ if (count() == 0)
+ break;
+
+ // special case arrays of ints and floats
auto item = m_items.begin();
auto last = std::prev(m_items.end());
- for (u32 rep = 0; rep < count(); rep++)
+ if (item == last && item->is_int_or_float())
{
- offset += item->save_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
- if (item != last)
- ++item;
+ u32 size = std::min<u32>(count() * item->native_size(), (offset < length) ? length - offset : 0);
+ memcpy(&ptr[offset], reinterpret_cast<void const *>(objbase), size);
+ offset += count() * item->native_size();
+ }
+ else
+ {
+ for (u32 rep = 0; rep < count(); rep++)
+ {
+ offset += item->save_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
+ if (item != last)
+ ++item;
+ }
}
break;
}
@@ -1533,6 +1641,11 @@ u64 save_registered_item::restore_binary(u8 const *ptr, u64 length, uintptr_t ob
if (unwrap_and_update_base(objbase))
return m_items.front().restore_binary(ptr, length, objbase);
+ // only containers are allowed to have null bases past this point; treat
+ // everything else as non-existent
+ if (objbase == 0 && type() != TYPE_CONTAINER)
+ return 0;
+
// switch off the type
u64 offset = 0;
switch (type())
@@ -1554,8 +1667,8 @@ u64 save_registered_item::restore_binary(u8 const *ptr, u64 length, uintptr_t ob
break;
// structs and containers iterate over owned items
- case TYPE_CONTAINER:
case TYPE_STRUCT:
+ case TYPE_CONTAINER:
for (auto &item : m_items)
offset += item.restore_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase);
break;
@@ -1565,13 +1678,27 @@ u64 save_registered_item::restore_binary(u8 const *ptr, u64 length, uintptr_t ob
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
{
+ // nothing to do if count is 0
+ if (count() == 0)
+ break;
+
+ // special case arrays of ints and floats
auto item = m_items.begin();
auto last = std::prev(m_items.end());
- for (u32 rep = 0; rep < count(); rep++)
+ if (item == last && item->is_int_or_float())
+ {
+ u32 size = std::min<u32>(count() * item->native_size(), (offset < length) ? length - offset : 0);
+ memcpy(reinterpret_cast<void *>(objbase), &ptr[offset], size);
+ offset += count() * item->native_size();
+ }
+ else
{
- offset += item->restore_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
- if (item != last)
- ++item;
+ for (u32 rep = 0; rep < count(); rep++)
+ {
+ offset += item->restore_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
+ if (item != last)
+ ++item;
+ }
}
break;
}
@@ -1593,6 +1720,14 @@ void save_registered_item::save_json(save_zip_state &zipstate, int indent, bool
// output the name if present
zipstate.json_append_name(m_name.c_str());
+ // only containers are allowed to have null bases past this point; treat
+ // everything else as non-existent
+ if (objbase == 0 && type() != TYPE_CONTAINER)
+ {
+ zipstate.json_append('n').json_append('u').json_append('l').json_append('l');
+ return;
+ }
+
// switch off the type
switch (type())
{
@@ -1652,6 +1787,13 @@ void save_registered_item::save_json(save_zip_state &zipstate, int indent, bool
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
{
+ // zero count is just an empty array
+ if (count() == 0)
+ {
+ zipstate.json_append('[').json_append(']');
+ break;
+ }
+
// look for large arrays of ints/floats
u32 total, unitsize;
if (is_endpoint_array(total, unitsize) && total * unitsize >= save_zip_state::JSON_EXTERNAL_BINARY_THRESHOLD)
@@ -1733,145 +1875,235 @@ void save_registered_item::restore_json(load_zip_state &input, json_restore_mode
if (unwrap_and_update_base(objbase))
return m_items.front().restore_json(input, mode, objbase);
+ // "null" is a placeholder for a missing struct or array item; just parse and return
+ // which will leave the item alone and allow us to move onto the next
+ if (input.json_matches("null"))
+ return;
+
+ // only containers are allowed to have null bases past this point; treat
+ // everything else as non-existent
+ if (objbase == 0 && type() != TYPE_CONTAINER)
+ return;
+
// switch off the type
switch (type())
{
// boolean types
case TYPE_BOOL:
{
+ bool valid = false;
bool value = false;
+
+ // must match 'true' or 'false'
if (input.json_matches("true"))
- value = true;
+ value = true, valid = true;
else if (input.json_matches("false"))
- value = false;
- else
- input.report_error(STATERR_MALFORMED_JSON, "%s: Unknown boolean value", full_name().c_str());
- if (mode == RESTORE_DATA)
+ value = false, valid = true;
+
+ // warn if not valid, otherwise process
+ if (!valid)
+ input.report_warning_and_consume(*this, "Expected boolean value, ignoring");
+ else if (mode == RESTORE_DATA)
write_bool(objbase, value);
else if (mode == COMPARE_DATA && read_bool(objbase) != value)
- input.report_warning("%s: Compare failed: JSON says %d, data says %d", full_name().c_str(), value, read_bool(objbase));
+ input.report_warning(*this, "Compare failed: JSON says %d, data says %d", value, read_bool(objbase));
break;
}
// signed integral types
case TYPE_INT:
{
+ bool dvalid = false, ivalid = false;
+ std::string svalue;
double dvalue;
- s64 ivalue;
+ s64 ivalue = 0;
+
+ // attempt to parse as a double, and then as a string with an integer
if (input.json_parse_number(dvalue))
+ dvalid = true;
+ else if (input.json_parse_string(svalue))
{
- if (mode == RESTORE_DATA && !write_int_signed(objbase, m_native_size, dvalue))
- input.report_warning("%s: Value of out range: %1.17g", full_name().c_str(), dvalue);
- else if (mode == COMPARE_DATA && read_int_signed(objbase, m_native_size) != s64(dvalue))
- input.report_warning("%s: Compare failed: JSON says %I64d, data says %1.17g", full_name().c_str(), dvalue, read_int_signed(objbase, m_native_size));
+ char *end;
+ ivalue = strtoll(&svalue[0], &end, 10);
+ ivalid = (end != &svalue[0]);
}
- else if (input.json_parse_int_string(ivalue))
+
+ // warn if not valid, otherwise process
+ if (!dvalid && !ivalid)
+ input.report_warning_and_consume(*this, "Expected integer value, ignoring");
+ else if (mode == RESTORE_DATA)
{
- if (mode == RESTORE_DATA && !write_int_signed(objbase, m_native_size, ivalue))
- input.report_warning("%s: Value of out range: %I64d", full_name().c_str(), ivalue);
- else if (mode == COMPARE_DATA && read_int_signed(objbase, m_native_size) != ivalue)
- input.report_warning("%s: Compare failed: JSON says %I64d, data says %I64d", full_name().c_str(), ivalue, read_int_signed(objbase, m_native_size));
+ if (dvalid && !write_int_signed(objbase, m_native_size, dvalue))
+ input.report_warning(*this, "Value of out range: %g", dvalue);
+ else if (ivalid && !write_int_signed(objbase, m_native_size, ivalue))
+ input.report_warning(*this, "Value of out range: %I64d", ivalue);
+ }
+ else if (mode == COMPARE_DATA)
+ {
+ if (dvalid && read_int_signed(objbase, m_native_size) != s64(dvalue))
+ input.report_warning(*this, "Compare failed: JSON says %g, data says %I64d", dvalue, read_int_signed(objbase, m_native_size));
+ else if (ivalid && read_int_signed(objbase, m_native_size) != ivalue)
+ input.report_warning(*this, "Compare failed: JSON says %I64d, data says %I64d", ivalue, read_int_signed(objbase, m_native_size));
}
- else
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected integer value", full_name().c_str());
break;
}
// unsigned integral types
case TYPE_UINT:
{
+ bool dvalid = false, ivalid = false;
+ std::string svalue;
double dvalue;
- u64 ivalue;
+ u64 ivalue = 0;
+
+ // attempt to parse as a double, and then as a string with an integer
if (input.json_parse_number(dvalue))
+ dvalid = true;
+ else if (input.json_parse_string(svalue))
{
- if (mode == RESTORE_DATA && !write_int_unsigned(objbase, m_native_size, dvalue))
- input.report_warning("%s: Value of out range: %1.17g", full_name().c_str(), dvalue);
- else if (mode == COMPARE_DATA && read_int_unsigned(objbase, m_native_size) != u64(dvalue))
- input.report_warning("%s: Compare failed: JSON says %I64d, data says %1.17g", full_name().c_str(), dvalue, read_int_unsigned(objbase, m_native_size));
+ char *end;
+ ivalue = strtoull(&svalue[0], &end, 10);
+ ivalid = (end != &svalue[0]);
}
- else if (input.json_parse_uint_string(ivalue))
+
+ // warn if not valid, otherwise process
+ if (!dvalid && !ivalid)
+ input.report_warning_and_consume(*this, "Expected integer value, ignoring");
+ else if (mode == RESTORE_DATA)
{
- if (mode == RESTORE_DATA && !write_int_unsigned(objbase, m_native_size, ivalue))
- input.report_warning("%s: Value of out range: %I64d", full_name().c_str(), ivalue);
- else if (mode == COMPARE_DATA && read_int_unsigned(objbase, m_native_size) != ivalue)
- input.report_warning("%s: Compare failed: JSON says %I64d, data says %I64d", full_name().c_str(), ivalue, read_int_unsigned(objbase, m_native_size));
+ if (dvalid && !write_int_unsigned(objbase, m_native_size, dvalue))
+ input.report_warning(*this, "Value of out range: %1.17g", dvalue);
+ else if (ivalid && !write_int_unsigned(objbase, m_native_size, ivalue))
+ input.report_warning(*this, "Value of out range: %I64u", ivalue);
+ }
+ else if (mode == COMPARE_DATA)
+ {
+ if (dvalid && read_int_unsigned(objbase, m_native_size) != u64(dvalue))
+ input.report_warning(*this, "Compare failed: JSON says %1.17g, data says %I64u", dvalue, read_int_unsigned(objbase, m_native_size));
+ else if (ivalid && read_int_unsigned(objbase, m_native_size) != ivalue)
+ input.report_warning(*this, "Compare failed: JSON says %I64u, data says %I64u", ivalue, read_int_unsigned(objbase, m_native_size));
}
- else
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected integer value", full_name().c_str());
break;
}
// float types
case TYPE_FLOAT:
{
+ bool valid = false;
double value;
- if (!input.json_parse_number(value))
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected number", full_name().c_str());
- if (mode == RESTORE_DATA && !write_float(objbase, m_native_size, value))
- input.report_warning("%s: Value of out range: %1.17g", full_name().c_str(), value);
+
+ // attempt to parse as a double
+ if (input.json_parse_number(value))
+ valid = true;
+
+ // warn if not valid, otherwise process
+ if (!valid)
+ input.report_warning_and_consume(*this, "Expected number, ignoring");
+ else if (mode == RESTORE_DATA && !write_float(objbase, m_native_size, value))
+ input.report_warning(*this, "Value of out range: %1.17g", value);
else if (mode == COMPARE_DATA && read_float(objbase, m_native_size) != value)
- input.report_warning("%s: Compare failed: JSON says %1.17g, data says %1.17g", full_name().c_str(), value, read_float(objbase, m_native_size));
+ input.report_warning(*this, "Compare failed: JSON says %1.17g, data says %1.17g", value, read_float(objbase, m_native_size));
break;
}
// structs and containers iterate over owned items
case TYPE_CONTAINER:
case TYPE_STRUCT:
+
+ // must start with an open brace
if (!input.json_matches('{'))
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected '{'", full_name().c_str());
- if (!input.json_matches('}'))
- while (1)
+ input.report_warning_and_consume(*this, "Expected structure, ignoring");
+ else
+ {
+ // track which items we've found
+ std::vector<bool> found(m_items.size(), false);
+ int numfound = 0;
+ if (!input.json_matches('}'))
+ while (1)
+ {
+ // parse the name
+ std::string name;
+ if (!input.json_parse_string(name) || name == "")
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected name within struct");
+
+ // followed by a colon
+ if (!input.json_matches(':'))
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ':'");
+
+ // look up the name; warn if not found or if we already got that one;
+ // otherwise process and mark found
+ u32 index;
+ save_registered_item *target = find(name.c_str(), index);
+ if (target == nullptr)
+ input.report_warning_and_consume(*this, "Found extraneous item '%s'", name.c_str());
+ else if (found[index])
+ input.report_warning_and_consume(*this, "Found duplicate item '%s'", name.c_str());
+ else
+ {
+ found[index] = true;
+ numfound++;
+ target->restore_json(input, mode, objbase);
+ }
+
+ // end with close brace, or a comma to indicate more
+ if (input.json_matches('}'))
+ break;
+ if (!input.json_matches(','))
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ','");
+ }
+
+ // report any missing items
+ if (numfound != m_items.size())
{
- std::string name;
- if (!input.json_parse_string(name) || name == "")
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected name within struct", full_name().c_str());
- if (!input.json_matches(':'))
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ':'", full_name().c_str());
- save_registered_item *target = find(name.c_str());
- if (target == nullptr)
- input.report_warning("%s: Found extraneous item '%s'", full_name().c_str(), name.c_str());
- target->restore_json(input, (target == nullptr) ? PARSE_ONLY : mode, objbase);
- if (input.json_matches('}'))
- break;
- if (!input.json_matches(','))
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ','", full_name().c_str());
+ int index = 0;
+ for (auto &item : m_items)
+ if (!found[index++])
+ input.report_warning(*this, "Missing item '%s'", item.name());
}
+ }
break;
// arrays are multiples of a single item
case TYPE_STATIC_ARRAY:
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
- {
- auto item = m_items.begin();
- auto last = std::prev(m_items.end());
+
+ // must start with an open bracket
if (!input.json_matches('['))
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected '['", full_name().c_str());
- if (parse_external_data(input, mode, objbase))
+ input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "Expected '['");
+
+ // handle empty arrays or external data
+ if (count() == 0 || parse_external_data(input, mode, objbase))
{
if (!input.json_matches(']'))
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ']'", full_name().c_str());
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ']'");
}
else
{
+ auto item = m_items.begin();
+ auto last = std::prev(m_items.end());
u32 rep = 0;
if (!input.json_matches(']'))
while (1)
{
+ // parse the next item
item->restore_json(input, (rep >= count()) ? PARSE_ONLY : mode, objbase + rep * m_native_size);
rep++;
if (item != last)
++item;
+
+ // end with close bracket, or a comma to indicate more
if (input.json_matches(']'))
break;
if (!input.json_matches(','))
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ','", full_name().c_str());
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ','");
}
+
+ // report missing or extraneous items
if (rep != count())
- input.report_warning("%s: Found %s array items than expected", full_name().c_str(), (rep < count()) ? "fewer" : "more");
+ input.report_warning(*this, "Found %s array items than expected", (rep < count()) ? "fewer" : "more");
}
break;
- }
}
}
@@ -1899,9 +2131,9 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
{
std::string name;
if (!input.json_parse_string(name) || name == "")
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected name within struct", full_name().c_str());
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected name within struct");
if (!input.json_matches(':'))
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ':'", full_name().c_str());
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ':'");
valid = false;
if (name == "external_file" && parsed_filename == "" && input.json_parse_string(parsed_filename) && parsed_filename != "")
valid = true;
@@ -1921,7 +2153,7 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
if (input.json_matches('}'))
break;
if (!input.json_matches(','))
- input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ','", full_name().c_str());
+ input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ','");
}
if (!valid)
{
@@ -1933,11 +2165,11 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
u64 offset;
u32 size;
if (!input.find_file(parsed_filename.c_str(), offset, size))
- input.report_error(STATERR_MISSING_FILE, "%s: Unable to find file '%s' in archive", full_name().c_str(), parsed_filename.c_str());
+ input.report_error(*this, STATERR_MISSING_FILE, "Unable to find file '%s' in archive", parsed_filename.c_str());
if (parsed_unit != 1 && parsed_unit != 2 && parsed_unit != 4 && parsed_unit != 8)
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Invalid unit size for external file, expected 1, 2, 4, or 8", full_name().c_str());
+ input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "Invalid unit size for external file, expected 1, 2, 4, or 8");
if (parsed_count == 0)
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Invalid count for external file", full_name().c_str());
+ input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "Invalid count for external file");
// look for the innermost registered item
save_registered_item *inner = &m_items.front();
@@ -1948,9 +2180,9 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
inner = &inner->subitems().front();
}
if (!inner->is_int_or_float())
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: External file specified, but not valid for this type", full_name().c_str());
+ input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "External file specified, but not valid for this type");
if (parsed_unit != inner->native_size())
- input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: External file has mismatched unit size", full_name().c_str());
+ input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "External file has mismatched unit size");
// if parseonly, we're done
if (parseonly)
@@ -1960,7 +2192,7 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
bool flip = (parsed_little_endian != native_little_endian);
zlib_read_streamer reader(input.file());
if (!reader.begin(offset) || !input.read_data_recursive(reader, *this, flip, size, objbase) || !reader.end())
- input.report_error(STATERR_READ_ERROR, "%s: Error reading file '%s' in archive", full_name().c_str(), parsed_filename.c_str());
+ input.report_error(*this, STATERR_READ_ERROR, "Error reading file '%s' in archive", parsed_filename.c_str());
return true;
}
@@ -2258,6 +2490,7 @@ void save_manager::allow_registration(bool allowed)
m_root_item.sort_and_prune();
// dump out a sample JSON
+ if (DUMP_INITIAL_JSON_SAVE)
{
save_zip_state state;
m_root_item.save_json(state);
diff --git a/src/emu/save.h b/src/emu/save.h
index 61a5223a387..7d26c9382f3 100644
--- a/src/emu/save.h
+++ b/src/emu/save.h
@@ -144,6 +144,7 @@ public:
save_registered_item &append(uintptr_t ptr_offset, save_type type, u32 native_size, char const *name, u32 count = 0);
// find an item by name
+ save_registered_item *find(char const *name, u32 &index);
save_registered_item *find(char const *name);
// is this item replicatable (i.e., can we replicate it for all elements in an array?)
@@ -285,11 +286,9 @@ public:
template<typename T>
std::enable_if_t<!std::is_array<T>::value, save_registrar> &reg(std::unique_ptr<T> &data, char const *name)
{
- if (data.get() == nullptr)
- throw emu_fatalerror("Passed null pointer to save state registration.");
-
save_registrar container(*this, &data, save_registered_item::TYPE_UNIQUE, sizeof(data), name, 0, data.get(), sizeof(T));
- container.reg(*data.get(), name);
+ if (data.get() != nullptr)
+ container.reg(*data.get(), name);
return *this;
}
@@ -319,10 +318,6 @@ public:
template<typename T>
save_registrar &reg(std::vector<T> &data, char const *name)
{
- // skip if no items
- if (data.size() == 0)
- return *this;
-
// create an outer container for the vector, then a regular array container within
save_registrar container(*this, &data, save_registered_item::TYPE_VECTOR, sizeof(data), name, 0, &data[0], sizeof(T));
container.register_array(&data[0], save_registered_item::TYPE_VECTOR_ARRAY, name, data.size());
@@ -333,10 +328,6 @@ public:
template<typename T>
save_registrar &reg(std::unique_ptr<T[]> &data, char const *name, std::size_t count)
{
- // skip if no items
- if (count == 0)
- return *this;
-
// create an outer container for the unique_ptr, then a regular array container within
save_registrar container(*this, &data, save_registered_item::TYPE_UNIQUE, sizeof(data), name, 0, data.get(), sizeof(T));
container.register_array(&data[0], save_registered_item::TYPE_RAW_ARRAY, name, count);
@@ -384,17 +375,14 @@ private:
template<typename T>
save_registrar &register_array(T *data, save_registered_item::save_type type, char const *name, std::size_t count)
{
- // skip 0-length items
- if (count == 0)
- return *this;
-
- // error on null pointer
- if (data == nullptr)
- throw emu_fatalerror("Passed null pointer to save state registration.");
+ // nullptr only allowed if the count is 0
+ if (data == nullptr && count != 0)
+ throw emu_fatalerror("Save: registered pointer with a null pointer (%s.%s)", m_item.full_name().c_str(), name);
// create a container and register the first item
save_registrar container(*this, data, type, uintptr_t(&data[1]) - uintptr_t(&data[0]), name, count, &data[0], sizeof(T));
- container.reg(data[0], "");
+ if (count != 0)
+ container.reg(data[0], "");
// if the first item was non-replicatable, register remaining items independently
if (!container.m_item.subitems().front().is_replicatable(true))