diff --git a/include/flamegpu/io/JSONStateReader.h b/include/flamegpu/io/JSONStateReader.h index 1f188b7fe..7056ee5f6 100644 --- a/include/flamegpu/io/JSONStateReader.h +++ b/include/flamegpu/io/JSONStateReader.h @@ -3,13 +3,8 @@ #include #include -#include -#include -#include #include "flamegpu/io/StateReader.h" -#include "flamegpu/model/ModelDescription.h" -#include "flamegpu/util/StringPair.h" namespace flamegpu { namespace io { @@ -20,33 +15,12 @@ namespace io { class JSONStateReader : public StateReader { public: /** - * Constructs a reader capable of reading model state from JSON files - * Environment properties will be read into the Simulation instance pointed to by 'sim_instance_id' - * Agent data will be read into 'model_state' - * @param model_name Name from the model description hierarchy of the model to be loaded - * @param env_desc Environment description for validating property data on load - * @param env_init Dictionary of loaded values map: - * @param macro_env_desc Macro environment description for validating property data on load - * @param macro_env_init Dictionary of loaded values map: - * @param model_state Map of AgentVector to load the agent data into per agent, key should be agent name - * @param input_file Filename of the input file (This will be used to determine which reader to return) - * @param sim_instance Instance of the Simulation object (This is used for setting/getting config) - */ - JSONStateReader( - const std::string &model_name, - const std::unordered_map &env_desc, - std::unordered_map &env_init, - const std::unordered_map ¯o_env_desc, - std::unordered_map> ¯o_env_init, - util::StringPairUnorderedMap> &model_state, - const std::string &input_file, - Simulation *sim_instance); - /** - * Actual performs the XML parsing to load the model state - * @return Always 0 - * @throws exception::RapidJSONError If parsing of the input file fails - */ - int parse() override; + * Loads the specified XML file to an internal data-structure + * @param input_file Path to file to be read + * @param model Model description to ensure file loaded is suitable + * @param verbosity Verbosity level to use during load + */ + void parse(const std::string &input_file, const std::shared_ptr &model, Verbosity verbosity) override; }; } // namespace io } // namespace flamegpu diff --git a/include/flamegpu/io/StateReader.h b/include/flamegpu/io/StateReader.h index d7127dfe3..6b8928ea4 100644 --- a/include/flamegpu/io/StateReader.h +++ b/include/flamegpu/io/StateReader.h @@ -4,12 +4,12 @@ #include #include #include -#include #include +#include #include "flamegpu/util/StringPair.h" -#include "flamegpu/model/EnvironmentData.h" #include "flamegpu/simulation/Simulation.h" +#include "flamegpu/simulation/CUDASimulation.h" namespace flamegpu { @@ -24,60 +24,61 @@ namespace io { */ class StateReader { public: - /** - * Constructs a reader capable of reading model state from a specific format (this class is abstract) - * Environment properties will be read into the Simulation instance pointed to by 'sim_instance_id' - * Agent data will be read into 'model_state' - * @param _model_name Name from the model description hierarchy of the model to be loaded - * @param _env_desc Environment description for validating property data on load - * @param _env_init Dictionary of loaded values map: - * @param _macro_env_desc Macro environment description for validating property data on load - * @param _macro_env_init Dictionary of loaded values map: - * @param _model_state Map of AgentVector to load the agent data into per agent, key should be agent name - * @param input Filename of the input file (This will be used to determine which reader to return) - * @param _sim_instance Instance of the simulation (for configuration data IO) - */ - StateReader( - const std::string& _model_name, - const std::unordered_map& _env_desc, - std::unordered_map& _env_init, - const std::unordered_map& _macro_env_desc, - std::unordered_map>& _macro_env_init, - util::StringPairUnorderedMap>& _model_state, - const std::string& input, - Simulation* _sim_instance) - : model_state(_model_state) - , inputFile(input) - , model_name(_model_name) - , env_desc(_env_desc) - , env_init(_env_init) - , macro_env_desc(_macro_env_desc) - , macro_env_init(_macro_env_init) - , sim_instance(_sim_instance) {} /** * Virtual destructor for correct inheritance behaviour */ virtual ~StateReader() {} + /** + * Loads the file to an internal data-structure + * @param input_file Path to file to be read + * @param model Model description to ensure file loaded is suitable + * @param verbosity Verbosity level to use during load + */ + virtual void parse(const std::string &input_file, const std::shared_ptr &model, Verbosity verbosity) = 0; + // ----------------------------------------------------------------------- - // The interface + // The Easy Interface // ----------------------------------------------------------------------- /** - * Actually perform the file load - * @return Returns a return code - * @todo: This should probably be the same return code between subclasses, and seems redundant with our exceptions as should never return fail. + * Grab the full simulation state from the input file + * @note CUDASimulation Config is not included and should be requested separately */ - virtual int parse() = 0; + void getFullModelState( + Simulation::Config &s_cfg, + std::unordered_map &environment_init, + std::unordered_map> ¯o_environment_init, + util::StringPairUnorderedMap> &agents_init); + + // ----------------------------------------------------------------------- + // The Advanced Interface + // ----------------------------------------------------------------------- + /** + * Overwrite the provided simulation config with the one loaded from file + * @param cfg The config struct to be overwritten + * @throws If the parsed file did not contain a simulation config or a file has not been parsed + */ + void getSimulationConfig(Simulation::Config &cfg); + /** + * Overwrite the provided CUDA config with the one loaded from file + * @param cfg The config struct to be overwritten + * @throws If the parsed file did not contain a CUDA config or a file has not been parsed + */ + void getCUDAConfig(CUDASimulation::Config &cfg); + void getEnvironment(std::unordered_map &environment_init); + void getMacroEnvironment(std::unordered_map> ¯o_environment_init); + void getAgents(util::StringPairUnorderedMap> &agents_init); protected: - util::StringPairUnorderedMap> &model_state; - std::string inputFile; - const std::string model_name; - const std::unordered_map &env_desc; - std::unordered_map& env_init; - const std::unordered_map ¯o_env_desc; - std::unordered_map>& macro_env_init; - Simulation *sim_instance; + std::string input_filepath; + + void resetCache(); + + std::unordered_map simulation_config; + std::unordered_map cuda_config; + std::unordered_map env_init; + std::unordered_map> macro_env_init; + util::StringPairUnorderedMap> agents_map; }; } // namespace io } // namespace flamegpu diff --git a/include/flamegpu/io/StateReaderFactory.h b/include/flamegpu/io/StateReaderFactory.h index 050870257..229750624 100644 --- a/include/flamegpu/io/StateReaderFactory.h +++ b/include/flamegpu/io/StateReaderFactory.h @@ -26,33 +26,17 @@ class StateReaderFactory { public: /** * Returns a reader capable of reading 'input' - * Environment properties will be read into the Simulation instance pointed to by 'sim_instance_id' - * Agent data will be read into 'model_state' - * @param model_name Name from the model description hierarchy of the model to be loaded - * @param env_desc Environment description for validating property data on load - * @param env_init Dictionary of loaded values map: - * @param macro_env_desc Macro environment description for validating property data on load - * @param macro_env_init Dictionary of loaded values map: - * @param model_state Map of AgentVector to load the agent data into per agent, key should be agent name * @param input Filename of the input file (This will be used to determine which reader to return) - * @param sim_instance Instance of the Simulation object (This is used for setting/getting config) * @throws exception::UnsupportedFileType If the file extension does not match an appropriate reader */ static StateReader* createReader( - const std::string& model_name, - const std::unordered_map& env_desc, - std::unordered_map& env_init, - const std::unordered_map& macro_env_desc, - std::unordered_map>& macro_env_init, - util::StringPairUnorderedMap>& model_state, - const std::string& input, - Simulation* sim_instance) { + const std::string& input) { const std::string extension = std::filesystem::path(input).extension().string(); if (extension == ".xml") { - return new XMLStateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance); + return new XMLStateReader(); } else if (extension == ".json") { - return new JSONStateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance); + return new JSONStateReader(); } THROW exception::UnsupportedFileType("File '%s' is not a type which can be read " "by StateReaderFactory::createReader().", diff --git a/include/flamegpu/io/XMLStateReader.h b/include/flamegpu/io/XMLStateReader.h index af6deaffc..d36252840 100644 --- a/include/flamegpu/io/XMLStateReader.h +++ b/include/flamegpu/io/XMLStateReader.h @@ -3,13 +3,8 @@ #include #include -#include -#include -#include #include "flamegpu/io/StateReader.h" -#include "flamegpu/model/ModelDescription.h" -#include "flamegpu/util/StringPair.h" namespace flamegpu { namespace io { @@ -19,40 +14,19 @@ namespace io { class XMLStateReader : public StateReader { public: /** - * Constructs a reader capable of reading model state from XML files - * Environment properties will be read into the Simulation instance pointed to by 'sim_instance_id' - * Agent data will be read into 'model_state' - * @param model_name Name from the model description hierarchy of the model to be loaded - * @param env_desc Environment description for validating property data on load - * @param env_init Dictionary of loaded values map:<{name, index}, value> - * @param macro_env_desc Macro environment description for validating property data on load - * @param macro_env_init Dictionary of loaded values map: - * @param model_state Map of AgentVector to load the agent data into per agent, key should be agent name - * @param input_file Filename of the input file (This will be used to determine which reader to return) - * @param sim_instance Instance of the Simulation object (This is used for setting/getting config) + * Loads the specified XML file to an internal data-structure + * @param input_file Path to file to be read + * @param model Model description to ensure file loaded is suitable + * @param verbosity Verbosity level to use during load */ - XMLStateReader( - const std::string &model_name, - const std::unordered_map &env_desc, - std::unordered_map &env_init, - const std::unordered_map ¯o_env_desc, - std::unordered_map> ¯o_env_init, - util::StringPairUnorderedMap> &model_state, - const std::string &input_file, - Simulation *sim_instance); - /** - * Actual performs the XML parsing to load the model state - * @return Always tinyxml2::XML_SUCCESS - * @throws exception::TinyXMLError If parsing of the input file fails - */ - int parse() override; + void parse(const std::string &input_file, const std::shared_ptr &model, Verbosity verbosity) override; private: /** * Flamegpu1 xml input files are allowed to omit state * This function extracts the initial state for the named agent from model_state; */ - std::string getInitialState(const std::string& agent_name) const; + static std::string getInitialState(const std::shared_ptr &model, const std::string& agent_name); }; } // namespace io } // namespace flamegpu diff --git a/include/flamegpu/model/ModelDescription.h b/include/flamegpu/model/ModelDescription.h index fd20d9a07..e07d4429e 100644 --- a/include/flamegpu/model/ModelDescription.h +++ b/include/flamegpu/model/ModelDescription.h @@ -37,6 +37,8 @@ class ModelDescription { friend class RunPlanVector; friend class RunPlan; friend class LoggingConfig; + friend class XMLStateReader; + friend class JSONStateReader; public: /** * Constructor diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efa999fb4..c740e7355 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -332,6 +332,7 @@ SET(SRC_FLAMEGPU ${FLAMEGPU_ROOT}/src/flamegpu/runtime/random/HostRandom.cu ${FLAMEGPU_ROOT}/src/flamegpu/io/JSONStateReader.cu ${FLAMEGPU_ROOT}/src/flamegpu/io/JSONStateWriter.cu + ${FLAMEGPU_ROOT}/src/flamegpu/io/StateReader.cu ${FLAMEGPU_ROOT}/src/flamegpu/io/XMLStateReader.cu ${FLAMEGPU_ROOT}/src/flamegpu/io/XMLStateWriter.cu ${FLAMEGPU_ROOT}/src/flamegpu/io/XMLLogger.cu diff --git a/src/flamegpu/io/JSONStateReader.cu b/src/flamegpu/io/JSONStateReader.cu index b817b172c..aec104ccb 100644 --- a/src/flamegpu/io/JSONStateReader.cu +++ b/src/flamegpu/io/JSONStateReader.cu @@ -12,23 +12,14 @@ #include "flamegpu/exception/FLAMEGPUException.h" #include "flamegpu/simulation/AgentVector.h" -#include "flamegpu/model/AgentDescription.h" +#include "flamegpu/model/AgentData.h" +#include "flamegpu/model/EnvironmentData.h" #include "flamegpu/simulation/CUDASimulation.h" #include "flamegpu/util/StringPair.h" namespace flamegpu { namespace io { -JSONStateReader::JSONStateReader( - const std::string &model_name, - const std::unordered_map &env_desc, - std::unordered_map &env_init, - const std::unordered_map& macro_env_desc, - std::unordered_map>& macro_env_init, - util::StringPairUnorderedMap> &model_state, - const std::string &input, - Simulation *sim_instance) - : StateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance) {} /** * This is the main sax style parser for the json state * It stores it's current position within the hierarchy with mode, lastKey and current_variable_array_index @@ -38,14 +29,11 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandler mode; std::string lastKey; std::string filename; - const std::unordered_map env_desc; + const std::shared_ptr& model; std::unordered_map &env_init; - const std::unordered_map macro_env_desc; std::unordered_map> ¯o_env_init; - /** - * Used for setting agent values - */ - util::StringPairUnorderedMap>&model_state; + util::StringPairUnorderedMap> &agents_map; + Verbosity verbosity; /** * Tracks current position reading variable array */ @@ -61,17 +49,18 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandler &_env_desc, + const std::shared_ptr &_model, std::unordered_map &_env_init, - const std::unordered_map & _macro_env_desc, std::unordered_map> & _macro_env_init, - util::StringPairUnorderedMap> &_model_state) + util::StringPairUnorderedMap> &_agents_map, + Verbosity _verbosity) : filename(_filename) - , env_desc(_env_desc) + , model(_model) , env_init(_env_init) - , macro_env_desc(_macro_env_desc) , macro_env_init(_macro_env_init) - , model_state(_model_state) { } + , agents_map(_agents_map) + , verbosity(_verbosity) { } + template bool processValue(const T val) { Mode isArray = Nop; @@ -80,8 +69,8 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandlerenvironment->properties.find(lastKey); + if (it == model->environment->properties.end()) { THROW exception::RapidJSONError("Input file contains unrecognised environment property '%s'," "in JSONStateReader::parse()\n", lastKey.c_str()); } @@ -123,8 +112,8 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandlerenvironment->macro_properties.find(lastKey); + if (it == model->environment->macro_properties.end()) { THROW exception::RapidJSONError("Input file contains unrecognised macro environment property '%s'," "in JSONStateReader::parse()\n", lastKey.c_str()); } @@ -167,7 +156,7 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandler &pop = model_state.at({current_agent, current_state}); + const std::shared_ptr &pop = agents_map.at({current_agent, current_state}); AgentVector::Agent instance = pop->back(); char *data = static_cast(const_cast(static_cast>(pop)->data(lastKey))); const VariableMap& agentVariables = pop->getVariableMetaData(); @@ -265,9 +254,9 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandlersecond->push_back(); } else { @@ -306,14 +295,14 @@ class JSONStateReader_impl : public rapidjson::BaseReaderHandlerenvironment->properties.at(lastKey); if (current_variable_array_index != prop.data.elements) { THROW exception::RapidJSONError("Input file contains environment property '%s' with %u elements expected %u," "in JSONStateReader::parse()\n", lastKey.c_str(), current_variable_array_index, prop.data.elements); } } else if (mode.top() == MacroEnvironment) { // Confirm macro env array had correct number of elements - const auto macro_prop = macro_env_desc.at(lastKey); + const auto macro_prop = model->environment->macro_properties.at(lastKey); const unsigned int macro_prop_elements = std::accumulate(macro_prop.elements.begin(), macro_prop.elements.end(), 1, std::multiplies()); if (current_variable_array_index != macro_prop_elements) { THROW exception::RapidJSONError("Input file contains environment macro property '%s' with %u elements expected %u," @@ -341,17 +330,22 @@ class JSONStateReader_agentsize_counter : public rapidjson::BaseReaderHandler agentstate_counts; - Simulation *sim_instance; - CUDASimulation *cudamodel_instance; + std::unordered_map &simulation_config; + std::unordered_map &cuda_config; + Verbosity verbosity; public: util::StringPairUnorderedMap getAgentCounts() const { return agentstate_counts; } - explicit JSONStateReader_agentsize_counter(const std::string &_filename, Simulation *_sim_instance) + explicit JSONStateReader_agentsize_counter(const std::string &_filename, + std::unordered_map &_simulation_config, + std::unordered_map &_cuda_config, + Verbosity _verbosity) : filename(_filename) - , sim_instance(_sim_instance) - , cudamodel_instance(dynamic_cast(_sim_instance)) { } + , simulation_config(_simulation_config) + , cuda_config(_cuda_config) + , verbosity(_verbosity) { } template bool processValue(const T val) { @@ -361,38 +355,32 @@ class JSONStateReader_agentsize_counter : public rapidjson::BaseReaderHandlerSimulationConfig().truncate_log_files = static_cast(val); - } else if (lastKey == "random_seed") { - sim_instance->SimulationConfig().random_seed = static_cast(val); - } else if (lastKey == "steps") { - sim_instance->SimulationConfig().steps = static_cast(val); - } else if (lastKey == "timing") { - sim_instance->SimulationConfig().timing = static_cast(val); - } else if (lastKey == "verbosity") { - sim_instance->SimulationConfig().verbosity = static_cast(val); - } else if (lastKey == "console_mode") { + if (lastKey == "truncate_log_files") { + simulation_config.emplace(lastKey, static_cast(val)); + } else if (lastKey == "random_seed") { + simulation_config.emplace(lastKey, static_cast(val)); + } else if (lastKey == "steps") { + simulation_config.emplace(lastKey, static_cast(val)); + } else if (lastKey == "timing") { + simulation_config.emplace(lastKey, static_cast(val)); + } else if (lastKey == "verbosity") { + simulation_config.emplace(lastKey, static_cast(val)); + } else if (lastKey == "console_mode") { #ifdef FLAMEGPU_VISUALISATION - sim_instance->SimulationConfig().console_mode = static_cast(val); + simulation_config.emplace(lastKey, static_cast(val)); #else - if (static_cast(val) == false) { - fprintf(stderr, "Warning: Cannot disable 'console_mode' with input file '%s', FLAMEGPU2 library has not been built with visualisation support enabled.\n", filename.c_str()); - } + fprintf(stderr, "Warning: Cannot configure 'console_mode' with input file '%s', FLAMEGPU2 library has not been built with visualisation support enabled.\n", filename.c_str()); #endif - } else { - THROW exception::RapidJSONError("Unexpected simulation config item '%s' in input file '%s'.\n", lastKey.c_str(), filename.c_str()); - } + } else { + THROW exception::RapidJSONError("Unexpected simulation config item '%s' in input file '%s'.\n", lastKey.c_str(), filename.c_str()); } } else if (mode.top() == CUDACfg) { - if (cudamodel_instance) { - if (lastKey == "device_id") { - cudamodel_instance->CUDAConfig().device_id = static_cast(val); - } else if (lastKey == "inLayerConcurrency") { - cudamodel_instance->CUDAConfig().inLayerConcurrency = static_cast(val); - } else { - THROW exception::RapidJSONError("Unexpected CUDA config item '%s' in input file '%s'.\n", lastKey.c_str(), filename.c_str()); - } + if (lastKey == "device_id") { + cuda_config.emplace(lastKey, static_cast(val)); + } else if (lastKey == "inLayerConcurrency") { + cuda_config.emplace(lastKey, static_cast(val)); + } else { + THROW exception::RapidJSONError("Unexpected CUDA config item '%s' in input file '%s'.\n", lastKey.c_str(), filename.c_str()); } } else { // Not useful @@ -412,19 +400,15 @@ class JSONStateReader_agentsize_counter : public rapidjson::BaseReaderHandler(d); } bool String(const char*str, rapidjson::SizeType, bool) { if (mode.top() == SimCfg) { - if (sim_instance) { - if (lastKey == "input_file") { - if (filename != str && str[0] != '\0') - if (sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) - fprintf(stderr, "Warning: Input file '%s' refers to second input file '%s', this will not be loaded.\n", filename.c_str(), str); - // sim_instance->SimulationConfig().input_file = str; - } else if (lastKey == "step_log_file") { - sim_instance->SimulationConfig().step_log_file = str; - } else if (lastKey == "exit_log_file") { - sim_instance->SimulationConfig().exit_log_file = str; - } else if (lastKey == "common_log_file") { - sim_instance->SimulationConfig().common_log_file = str; - } + if (lastKey == "input_file") { + if (filename != str && str[0] != '\0') + if (verbosity > Verbosity::Quiet) + fprintf(stderr, "Warning: Input file '%s' refers to second input file '%s', this will not be loaded.\n", filename.c_str(), str); + // sim_instance->SimulationConfig().input_file = str; + } else if (lastKey == "step_log_file" || + lastKey == "exit_log_file" || + lastKey == "common_log_file") { + simulation_config.emplace(lastKey, std::string(str)); } } return true; @@ -500,36 +484,43 @@ class JSONStateReader_agentsize_counter : public rapidjson::BaseReaderHandler &model, Verbosity verbosity) { + resetCache(); + + std::ifstream in(input_file, std::ios::in | std::ios::binary); if (!in) { - THROW exception::RapidJSONError("Unable to open file '%s' for reading.\n", inputFile.c_str()); + THROW exception::RapidJSONError("Unable to open file '%s' for reading, in JSONStateReader::parse().", input_file.c_str()); } - JSONStateReader_agentsize_counter agentcounter(inputFile, sim_instance); - JSONStateReader_impl handler(inputFile, env_desc, env_init, macro_env_desc, macro_env_init, model_state); + JSONStateReader_agentsize_counter agentcounter(input_file, simulation_config, cuda_config, verbosity); + JSONStateReader_impl handler(input_file, model, env_init, macro_env_init, agents_map, verbosity); std::string filestring = std::string((std::istreambuf_iterator(in)), std::istreambuf_iterator()); rapidjson::StringStream filess(filestring.c_str()); rapidjson::Reader reader; // First parse the file and simply count the size of agent list rapidjson::ParseResult pr1 = reader.Parse(filess, agentcounter); if (pr1.Code() != rapidjson::ParseErrorCode::kParseErrorNone) { - THROW exception::RapidJSONError("Whilst parsing input file '%s', RapidJSON returned error: %s\n", inputFile.c_str(), rapidjson::GetParseError_En(pr1.Code())); + THROW exception::RapidJSONError("Whilst parsing input file '%s', RapidJSON returned error: %s\n", input_file.c_str(), rapidjson::GetParseError_En(pr1.Code())); } const util::StringPairUnorderedMap agentCounts = agentcounter.getAgentCounts(); // Use this to preallocate the agent statelists - for (auto &agt : agentCounts) { - auto f = model_state.find(agt.first); - if (f!= model_state.end()) - f->second->reserve(agt.second); + for (auto &it : agentCounts) { + const auto& agent = model->agents.find(it.first.first); + if (agent == model->agents.end() || agent->second->states.find(it.first.second) == agent->second->states.end()) { + THROW exception::InvalidAgentState("Agent '%s' with state '%s', found in input file '%s', is not part of the model description hierarchy, " + "in JSONStateReader::parse()\n Ensure the input file is for the correct model.\n", it.first.first.c_str(), it.first.second.c_str(), input_file.c_str()); + } + auto [_it, _] = agents_map.emplace(it.first, std::make_shared(*agent->second)); + _it->second->reserve(it.second); } // Reset the string stream filess = rapidjson::StringStream(filestring.c_str()); // Read in the file data rapidjson::ParseResult pr2 = reader.Parse(filess, handler); if (pr2.Code() != rapidjson::ParseErrorCode::kParseErrorNone) { - THROW exception::RapidJSONError("Whilst parsing input file '%s', RapidJSON returned error: %s\n", inputFile.c_str(), rapidjson::GetParseError_En(pr1.Code())); + THROW exception::RapidJSONError("Whilst parsing input file '%s', RapidJSON returned error: %s\n", input_file.c_str(), rapidjson::GetParseError_En(pr1.Code())); } - return 0; + // Mark input as loaded + this->input_filepath = input_file; } } // namespace io diff --git a/src/flamegpu/io/StateReader.cu b/src/flamegpu/io/StateReader.cu new file mode 100644 index 000000000..9e05d5627 --- /dev/null +++ b/src/flamegpu/io/StateReader.cu @@ -0,0 +1,96 @@ +#include "flamegpu/io/StateReader.h" + +namespace flamegpu { +namespace io { + +void StateReader::resetCache() { + simulation_config.clear(); + cuda_config.clear(); + env_init.clear(); + macro_env_init.clear(); + agents_map.clear(); +} +void StateReader::getFullModelState( + Simulation::Config &s_cfg, + std::unordered_map &environment_init, + std::unordered_map> ¯o_environment_init, + util::StringPairUnorderedMap> &agents_init) { + getSimulationConfig(s_cfg); + getEnvironment(environment_init); + getMacroEnvironment(macro_environment_init); + getAgents(agents_init); +} + +#define MAP_GET(out, map, name, typ) out.name = map.find(#name) == map.end()?out.name:std::any_cast(map.at(#name)) + +void StateReader::getSimulationConfig(Simulation::Config &cfg) { + if (input_filepath.empty()) { + THROW exception::InvalidOperation("Input file has not been parsed, in StateReader::getSimulationConfig()"); + } + // if (!simulation_config) { + // THROW exception::InvalidInputFile("Input file %s did not contain an simulation config, in StateReader::getSimulationConfig()", input_filepath.c_str()); + // } + // Set all the items manually + MAP_GET(cfg, simulation_config, input_file, std::string); + MAP_GET(cfg, simulation_config, step_log_file, std::string); + MAP_GET(cfg, simulation_config, exit_log_file, std::string); + MAP_GET(cfg, simulation_config, common_log_file, std::string); + MAP_GET(cfg, simulation_config, truncate_log_files, bool); + MAP_GET(cfg, simulation_config, random_seed, uint64_t); + MAP_GET(cfg, simulation_config, steps, unsigned int); + MAP_GET(cfg, simulation_config, verbosity, Verbosity); + MAP_GET(cfg, simulation_config, timing, bool); + MAP_GET(cfg, simulation_config, silence_unknown_args, bool); + MAP_GET(cfg, simulation_config, telemetry, bool); +#ifdef FLAMEGPU_VISUALISATION + MAP_GET(cfg, simulation_config, console_mode, bool); +#endif +} +void StateReader::getCUDAConfig(CUDASimulation::Config &cfg) { + if (input_filepath.empty()) { + THROW exception::InvalidOperation("Input file has not been parsed, in StateReader::getCUDAConfig()"); + } + // if (!cuda_config) { + // THROW exception::InvalidInputFile("Input file %s did not contain an CUDA config, in StateReader::getCUDAConfig()", input_filepath.c_str()); + // } + // Set all the items manually + MAP_GET(cfg, cuda_config, device_id, int); + MAP_GET(cfg, cuda_config, inLayerConcurrency, bool); +} +void StateReader::getEnvironment(std::unordered_map &environment_init) { + if (input_filepath.empty()) { + THROW exception::InvalidOperation("Input file has not been parsed, in StateReader::getEnvironment()"); + } + // if (env_init.empty()) { + // THROW exception::InvalidInputFile("Input file %s did not contain any environment properties, in StateReader::getEnvironment()", input_filepath.c_str()); + // } + for (const auto& [key, val] : env_init) { + environment_init.erase(key); + environment_init.emplace(key, val); + } +} +void StateReader::getMacroEnvironment(std::unordered_map> ¯o_environment_init) { + if (input_filepath.empty()) { + THROW exception::InvalidOperation("Input file has not been parsed, in StateReader::getEnvironment()"); + } + // if (macro_env_init.empty()) { + // THROW exception::InvalidInputFile("Input file %s did not contain any macro environment properties, in StateReader::getMacroEnvironment()", input_filepath.c_str()); + // } + for (const auto& [key, val] : macro_env_init) { + macro_environment_init.insert_or_assign(key, val); + } +} +void StateReader::getAgents(util::StringPairUnorderedMap> &agents_init) { + if (input_filepath.empty()) { + THROW exception::InvalidOperation("Input file has not been parsed, in StateReader::getEnvironment()"); + } + // if (agents_map.empty()) { + // THROW exception::InvalidInputFile("Input file %s did not contain any agents, in StateReader::getMacroEnvironment()", input_filepath.c_str()); + // } + for (const auto& [key, val] : agents_map) { + agents_init.insert_or_assign(key, val); + } +} + +} // namespace io +} // namespace flamegpu diff --git a/src/flamegpu/io/XMLStateReader.cu b/src/flamegpu/io/XMLStateReader.cu index 35bb1f22d..46a1dc2aa 100644 --- a/src/flamegpu/io/XMLStateReader.cu +++ b/src/flamegpu/io/XMLStateReader.cu @@ -1,13 +1,3 @@ - -/** - * @file - * @author - * @date - * @brief - * - * \todo longer description - */ - #include "flamegpu/io/XMLStateReader.h" #include #include @@ -17,6 +7,7 @@ #include "flamegpu/exception/FLAMEGPUException.h" #include "flamegpu/simulation/AgentVector.h" #include "flamegpu/model/AgentDescription.h" +#include "flamegpu/model/EnvironmentDescription.h" #include "flamegpu/simulation/CUDASimulation.h" namespace flamegpu { @@ -65,28 +56,7 @@ namespace io { } #endif -XMLStateReader::XMLStateReader( - const std::string &model_name, - const std::unordered_map &env_desc, - std::unordered_map &env_init, - const std::unordered_map& macro_env_desc, - std::unordered_map> ¯o_env_init, - util::StringPairUnorderedMap> &model_state, - const std::string &input, - Simulation *sim_instance) - : StateReader(model_name, env_desc, env_init, macro_env_desc, macro_env_init, model_state, input, sim_instance) {} - -std::string XMLStateReader::getInitialState(const std::string &agent_name) const { - for (const auto &i : model_state) { - if (agent_name == i.first.first) - return i.second->getInitialState(); - } - return ModelData::DEFAULT_STATE; -} -/** -* \brief parses the xml file -*/ -int XMLStateReader::parse() { +void XMLStateReader::parse(const std::string &inputFile, const std::shared_ptr &model, Verbosity verbosity) { tinyxml2::XMLDocument doc; tinyxml2::XMLError errorId = doc.LoadFile(inputFile.c_str()); @@ -101,90 +71,70 @@ int XMLStateReader::parse() { tinyxml2::XMLElement* pElement = pRoot->FirstChildElement("config"); if (pElement) { // Sim config - if (sim_instance) { - tinyxml2::XMLElement *pSimCfgBlock = pElement->FirstChildElement("simulation"); + tinyxml2::XMLElement *pSimCfgBlock = pElement->FirstChildElement("simulation"); + if (pSimCfgBlock) { for (auto simCfgElement = pSimCfgBlock->FirstChildElement(); simCfgElement; simCfgElement = simCfgElement->NextSiblingElement()) { std::string key = simCfgElement->Value(); std::string val = simCfgElement->GetText() ? simCfgElement->GetText() : ""; if (key == "input_file") { if (inputFile != val && !val.empty()) - if (sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) + if (verbosity > Verbosity::Quiet) fprintf(stderr, "Warning: Input file '%s' refers to second input file '%s', this will not be loaded.\n", inputFile.c_str(), val.c_str()); // sim_instance->SimulationConfig().input_file = val; - } else if (key == "step_log_file") { - sim_instance->SimulationConfig().step_log_file = val; - } else if (key == "exit_log_file") { - sim_instance->SimulationConfig().exit_log_file = val; - } else if (key == "common_log_file") { - sim_instance->SimulationConfig().common_log_file = val; - } else if (key == "truncate_log_files") { + } else if (key == "step_log_file" || + key == "exit_log_file" || + key == "common_log_file") { + simulation_config.emplace(key, val); + } else if (key == "truncate_log_files" || +#ifdef FLAMEGPU_VISUALISATION + key == "console_mode" || +#endif + key == "timing") { for (auto& c : val) c = static_cast(::tolower(c)); if (val == "true") { - sim_instance->SimulationConfig().truncate_log_files = true; + simulation_config.emplace(key, true); } else if (val == "false") { - sim_instance->SimulationConfig().truncate_log_files = false; + simulation_config.emplace(key, false); } else { - sim_instance->SimulationConfig().truncate_log_files = static_cast(stoll(val)); + simulation_config.emplace(key, static_cast(stoll(val))); } } else if (key == "random_seed") { - sim_instance->SimulationConfig().random_seed = static_cast(stoull(val)); + simulation_config.emplace(key, static_cast(stoull(val))); } else if (key == "steps") { - sim_instance->SimulationConfig().steps = static_cast(stoull(val)); + simulation_config.emplace(key, static_cast(stoull(val))); } else if (key == "verbosity") { - sim_instance->SimulationConfig().verbosity = static_cast(stoull(val)); - } else if (key == "timing") { - for (auto& c : val) - c = static_cast(::tolower(c)); - if (val == "true") { - sim_instance->SimulationConfig().timing = true; - } else if (val == "false") { - sim_instance->SimulationConfig().timing = false; - } else { - sim_instance->SimulationConfig().timing = static_cast(stoll(val)); - } - } else if (key == "console_mode") { -#ifdef FLAMEGPU_VISUALISATION - for (auto& c : val) - c = static_cast(::tolower(c)); - if (val == "true") { - sim_instance->SimulationConfig().console_mode = true; - } else if (val == "false") { - sim_instance->SimulationConfig().console_mode = false; - } else { - sim_instance->SimulationConfig().console_mode = static_cast(stoll(val)); - } -#else - if (val == "false") { - if (sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) - fprintf(stderr, "Warning: Cannot disable 'console_mode' with input file '%s', FLAMEGPU2 library has not been built with visualisation support enabled.\n", inputFile.c_str()); - } -#endif - } else if (sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + simulation_config.emplace(key, static_cast(stoull(val))); + } else if (verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Input file '%s' contains unexpected simulation config property '%s'.\n", inputFile.c_str(), key.c_str()); } +#ifndef FLAMEGPU_VISUALISATION + if (key == "console_mode") { + if (verbosity > Verbosity::Quiet) + fprintf(stderr, "Warning: Cannot configure 'console_mode' with input file '%s', FLAMEGPU2 library has not been built with visualisation support enabled.\n", inputFile.c_str()); + } +#endif } } // CUDA config - CUDASimulation *cudamodel_instance = dynamic_cast(sim_instance); - if (cudamodel_instance) { - tinyxml2::XMLElement *pCUDACfgBlock = pElement->FirstChildElement("cuda"); + tinyxml2::XMLElement *pCUDACfgBlock = pElement->FirstChildElement("cuda"); + if (pCUDACfgBlock) { for (auto cudaCfgElement = pCUDACfgBlock->FirstChildElement(); cudaCfgElement; cudaCfgElement = cudaCfgElement->NextSiblingElement()) { std::string key = cudaCfgElement->Value(); std::string val = cudaCfgElement->GetText(); if (key == "device_id") { - cudamodel_instance->CUDAConfig().device_id = static_cast(stoull(val)); + cuda_config.emplace(key, static_cast(stoull(val))); } else if (key == "inLayerConcurrency") { for (auto& c : val) c = static_cast(::tolower(c)); if (val == "true") { - cudamodel_instance->CUDAConfig().inLayerConcurrency = true; + cuda_config.emplace(key, true); } else if (val == "false") { - cudamodel_instance->CUDAConfig().inLayerConcurrency = false; + cuda_config.emplace(key, false); } else { - cudamodel_instance->CUDAConfig().inLayerConcurrency = static_cast(stoll(val)); + cuda_config.emplace(key, static_cast(stoll(val))); } - } else if (sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + } else if (verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Input file '%s' contains unexpected cuda config property '%s'.\n", inputFile.c_str(), key.c_str()); } } @@ -196,12 +146,13 @@ int XMLStateReader::parse() { // Read environment data pElement = pRoot->FirstChildElement("environment"); if (pElement) { + auto& env_props = model->environment->properties; for (auto envElement = pElement->FirstChildElement(); envElement; envElement = envElement->NextSiblingElement()) { const char *key = envElement->Value(); std::stringstream ss(envElement->GetText()); std::string token; - const auto it = env_desc.find(std::string(key)); - if (it == env_desc.end()) { + const auto it = env_props.find(std::string(key)); + if (it == env_props.end()) { THROW exception::TinyXMLError("Input file contains unrecognised environment property '%s'," "in XMLStateReader::parse()\n", key); } @@ -244,24 +195,25 @@ int XMLStateReader::parse() { "in XMLStateReader::parse()\n", key, val_type.name()); } } - if (el != elements && sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + if (el != elements && verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Environment array property '%s' expects '%u' elements, input file '%s' contains '%u' elements.\n", key, elements, inputFile.c_str(), el); } } - } else if (sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + } else if (verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Input file '%s' does not contain environment node.\n", inputFile.c_str()); } // Read macro environment data pElement = pRoot->FirstChildElement("macro_environment"); if (pElement) { + auto& env_macro_props = model->environment->macro_properties; for (auto envElement = pElement->FirstChildElement(); envElement; envElement = envElement->NextSiblingElement()) { const char *key = envElement->Value(); std::stringstream ss(envElement->GetText()); std::string token; - const auto it = macro_env_desc.find(std::string(key)); - if (it == macro_env_desc.end()) { + const auto it = env_macro_props.find(std::string(key)); + if (it == env_macro_props.end()) { THROW exception::TinyXMLError("Input file contains unrecognised macro environment property '%s'," "in XMLStateReader::parse()\n", key); } @@ -304,12 +256,12 @@ int XMLStateReader::parse() { "in XMLStateReader::parse()\n", key, val_type.name()); } } - if (el != elements && sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + if (el != elements && verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Macro environment property '%s' expects '%u' elements, input file '%s' contains '%u' elements.\n", key, elements, inputFile.c_str(), el); } } - } else if (sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + } else if (verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Input file '%s' does not contain macro environment node.\n", inputFile.c_str()); } @@ -318,14 +270,19 @@ int XMLStateReader::parse() { for (pElement = pRoot->FirstChildElement("xagent"); pElement != nullptr; pElement = pElement->NextSiblingElement("xagent")) { std::string agent_name = pElement->FirstChildElement("name")->GetText(); tinyxml2::XMLElement *state_element = pElement->FirstChildElement("state"); - std::string state_name = state_element ? state_element->GetText() : getInitialState(agent_name); + std::string state_name = state_element ? state_element->GetText() : getInitialState(model, agent_name); cts[{agent_name, state_name}]++; } - // Resize state lists (greedy, all lists are resized to max size of state) - for (auto& it : model_state) { - auto f = cts.find(it.first); - if (f != cts.end()) - it.second->reserve(f->second); + + // Init state lists to correct size + for (const auto& it : cts) { + const auto& agent = model->agents.find(it.first.first); + if (agent == model->agents.end() || agent->second->states.find(it.first.second) == agent->second->states.end()) { + THROW exception::InvalidAgentState("Agent '%s' with state '%s', found in input file '%s', is not part of the model description hierarchy, " + "in XMLStateReader::parse()\n Ensure the input file is for the correct model.\n", it.first.first.c_str(), it.first.second.c_str(), inputFile.c_str()); + } + auto [_it, _] = agents_map.emplace(it.first, std::make_shared(*agent->second)); + _it->second->reserve(it.second); } bool hasWarnedElements = false; @@ -337,9 +294,9 @@ int XMLStateReader::parse() { const char* agentName = pNameElement->GetText(); // Find agent state, use initial state if not set (means its old flame gpu 1 input file) tinyxml2::XMLElement* pStateElement = pElement->FirstChildElement("state"); - const std::string agentState = pStateElement ? std::string(pStateElement->GetText()) : getInitialState(agentName); - const auto agentIt = model_state.find({ agentName, agentState }); - if (agentIt == model_state.end()) { + const std::string agentState = pStateElement ? std::string(pStateElement->GetText()) : getInitialState(model, agentName); + const auto agentIt = agents_map.find({ agentName, agentState }); + if (agentIt == agents_map.end()) { THROW exception::InvalidAgentState("Agent '%s' with state '%s', found in input file '%s', is not part of the model description hierarchy, " "in XMLStateReader::parse()\n Ensure the input file is for the correct model.\n", agentName, agentState.c_str(), inputFile.c_str()); } @@ -398,12 +355,12 @@ int XMLStateReader::parse() { } } // Warn if var is wrong length - if (el != var_data.elements && !hasWarnedElements && sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + if (el != var_data.elements && !hasWarnedElements && verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Agent '%s' variable '%s' expects '%u' elements, input file '%s' contains '%u' elements.\n", agentName, variable_name.c_str(), var_data.elements, inputFile.c_str(), el); hasWarnedElements = true; } - } else if (!hasWarnedMissingVar && variable_name.find('_', 0) != 0 && sim_instance->getSimulationConfig().verbosity > Verbosity::Quiet) { + } else if (!hasWarnedMissingVar && variable_name.find('_', 0) != 0 && verbosity > Verbosity::Quiet) { fprintf(stderr, "Warning: Agent '%s' variable '%s' is missing from, input file '%s'.\n", agentName, variable_name.c_str(), inputFile.c_str()); hasWarnedMissingVar = true; @@ -411,8 +368,16 @@ int XMLStateReader::parse() { } } - return tinyxml2::XML_SUCCESS; + // Mark input as loaded + this->input_filepath = inputFile; } +std::string XMLStateReader::getInitialState(const std::shared_ptr &model, const std::string &agent_name) { + const auto& it = model->agents.find(agent_name); + if (it != model->agents.end()) { + return it->second->initial_state; + } + return ModelData::DEFAULT_STATE; +} } // namespace io } // namespace flamegpu diff --git a/src/flamegpu/simulation/Simulation.cu b/src/flamegpu/simulation/Simulation.cu index 663d37abb..f15b193b3 100644 --- a/src/flamegpu/simulation/Simulation.cu +++ b/src/flamegpu/simulation/Simulation.cu @@ -62,9 +62,13 @@ void Simulation::applyConfig() { env_init.clear(); macro_env_init.clear(); - io::StateReader *read__ = io::StateReaderFactory::createReader(model->name, model->environment->properties, env_init, model->environment->macro_properties, macro_env_init, pops, config.input_file.c_str(), this); + io::StateReader *read__ = io::StateReaderFactory::createReader(config.input_file); if (read__) { - read__->parse(); + read__->parse(config.input_file, model, config.verbosity); + read__->getFullModelState(config, env_init, macro_env_init, pops); + if (auto cuda_sim = dynamic_cast(this)) { + read__->getCUDAConfig(cuda_sim->CUDAConfig()); + } for (auto &agent : pops) { setPopulationData(*agent.second, agent.first.second); } @@ -190,18 +194,20 @@ int Simulation::checkArgs(int argc, const char** argv) { } env_init.clear(); macro_env_init.clear(); - const auto &env_desc = model->environment->properties; // For some reason this method returns a copy, not a reference - const auto ¯o_env_desc = model->environment->macro_properties; // For some reason this method returns a copy, not a reference - io::StateReader *read__ = io::StateReaderFactory::createReader(model->name, env_desc, env_init, macro_env_desc, macro_env_init, pops, config.input_file.c_str(), this); + io::StateReader *read__ = io::StateReaderFactory::createReader(config.input_file); if (read__) { try { - read__->parse(); + read__->parse(config.input_file, model, config.verbosity); + read__->getFullModelState(config, env_init, macro_env_init, pops); + if (auto cuda_sim = dynamic_cast(this)) { + read__->getCUDAConfig(cuda_sim->CUDAConfig()); + } } catch (const std::exception &e) { fprintf(stderr, "Loading input file '%s' failed!\nDetail: %s", config.input_file.c_str(), e.what()); return false; } for (auto &agent : pops) { - setPopulationData(*agent.second); + setPopulationData(*agent.second, agent.first.second); } } }