Skip to content

Commit

Permalink
RunPlanVector JSON IO minimum viable product (untested)
Browse files Browse the repository at this point in the history
  • Loading branch information
Robadob committed Sep 21, 2024
1 parent 42786c5 commit 48768e8
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 5 deletions.
25 changes: 25 additions & 0 deletions include/flamegpu/io/JSONRunPlanReader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef INCLUDE_FLAMEGPU_IO_JSONRUNPLANREADER_H_
#define INCLUDE_FLAMEGPU_IO_JSONRUNPLANREADER_H_

#include "flamegpu/simulation/RunPlanVector.h"

namespace flamegpu {
class ModelDescription;
namespace io {

/**
* JSON format reader of RunPlanVector
*/
class JSONRunPlanReader {
public:
/**
* Loads and returns the specified JSON file if contains a RunPlanVector
* @param input_filepath Path on disk to read the file from
* @param model The model used to initialise the RunPlanVector
*/
static RunPlanVector load(const std::string &input_filepath, const ModelDescription& model);

Check failure on line 20 in include/flamegpu/io/JSONRunPlanReader.h

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Add #include <string> for string
};
} // namespace io
} // namespace flamegpu

#endif // INCLUDE_FLAMEGPU_IO_JSONRUNPLANREADER_H_
35 changes: 35 additions & 0 deletions include/flamegpu/io/JSONRunPlanWriter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef INCLUDE_FLAMEGPU_IO_JSONRUNPLANWRITER_H_
#define INCLUDE_FLAMEGPU_IO_JSONRUNPLANWRITER_H_

#include <rapidjson/writer.h>

#include "flamegpu/simulation/RunPlanVector.h"

namespace flamegpu {
namespace io {
/**
* JSON format writer of RunPlanVector
*/
class JSONRunPlanWriter {
// Typedef for the writer used, as the full template specification is way too long
typedef rapidjson::Writer<rapidjson::StringBuffer, rapidjson::UTF8<>, rapidjson::UTF8<>, rapidjson::CrtAllocator, rapidjson::kWriteNanAndInfFlag> GenericJSONWriter;
/**
* Utility method for writing out a single RunPlan
* @param writer An initialised RapidJSON writer.
* @param rp RunPlan to be writer
*/
static void writeRunPlan(std::unique_ptr<GenericJSONWriter>& writer, const RunPlan& rp);

Check failure on line 21 in include/flamegpu/io/JSONRunPlanWriter.h

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Add #include <memory> for unique_ptr<>

public:
/**
* Exports the provided RunPlanVector in JSON format to the specified output_filepath
* @param rpv The RunPlanVector to be exported
* @param output_filepath Location on disk to export the file
* @param pretty Whether the exported JSON is "prettified" or "minified"
*/
static void save(const RunPlanVector &rpv, const std::string &output_filepath, bool pretty=true);

Check failure on line 30 in include/flamegpu/io/JSONRunPlanWriter.h

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Missing spaces around =

Check failure on line 30 in include/flamegpu/io/JSONRunPlanWriter.h

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Add #include <string> for string
};
} // namespace io
} // namespace flamegpu

#endif // INCLUDE_FLAMEGPU_IO_JSONRUNPLANWRITER_H_
2 changes: 1 addition & 1 deletion include/flamegpu/io/JSONStateReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace io {
class JSONStateReader : public StateReader {
public:
/**
* Loads the specified XML file to an internal data-structure
* Loads the specified JSON 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
Expand Down
5 changes: 4 additions & 1 deletion include/flamegpu/model/ModelDescription.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
#include "flamegpu/runtime/messaging/MessageBruteForce/MessageBruteForceHost.h"

namespace flamegpu {

namespace io {
class JSONRunPlanReader;
}
class AgentDescription;
class CAgentDescription;
class CLayerDescription;
Expand Down Expand Up @@ -39,6 +41,7 @@ class ModelDescription {
friend class LoggingConfig;
friend class XMLStateReader;
friend class JSONStateReader;
friend class io::JSONRunPlanReader;
public:
/**
* Constructor
Expand Down
8 changes: 8 additions & 0 deletions include/flamegpu/simulation/RunPlan.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class CUDASimulation;
namespace io {
class JSONLogger;
class XMLLogger;
class JSONRunPlanWriter;
class JSONRunPlanReader_impl;
} // namespace io

/**
Expand All @@ -35,6 +37,12 @@ class RunPlan {
friend class CUDASimulation;
friend class io::JSONLogger;
friend class io::XMLLogger;
friend class io::JSONRunPlanWriter;
friend class io::JSONRunPlanReader_impl;
/**
* Internal constructor used during file-io
*/
explicit RunPlan(const std::shared_ptr<const ModelData> &model);

public:
/**
Expand Down
11 changes: 10 additions & 1 deletion include/flamegpu/simulation/RunPlanVector.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@


namespace flamegpu {

namespace io {
class JSONRunPlanReader;
class JSONRunPlanReader_impl;
}
class ModelDescription;
class EnvironmentDescription;

Expand All @@ -27,6 +30,12 @@ class RunPlanVector : private std::vector<RunPlan> {
friend class RunPlan;
friend class detail::AbstractSimRunner;
friend unsigned int CUDAEnsemble::simulate(const RunPlanVector& plans);
friend class io::JSONRunPlanReader;
friend class io::JSONRunPlanReader_impl;
/**
* Internal constructor used during file-io
*/
explicit RunPlanVector(const std::shared_ptr<const ModelData> &model, unsigned int initial_length);

public:
/**
Expand Down
4 changes: 4 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ SET(SRC_INCLUDE
${FLAMEGPU_ROOT}/include/flamegpu/flamegpu.h
${FLAMEGPU_ROOT}/include/flamegpu/io/StateReader.h
${FLAMEGPU_ROOT}/include/flamegpu/io/StateWriter.h
${FLAMEGPU_ROOT}/include/flamegpu/io/JSONRunPlanReader.h
${FLAMEGPU_ROOT}/include/flamegpu/io/JSONRunPlanWriter.h
${FLAMEGPU_ROOT}/include/flamegpu/io/JSONStateReader.h
${FLAMEGPU_ROOT}/include/flamegpu/io/JSONStateWriter.h
${FLAMEGPU_ROOT}/include/flamegpu/io/XMLStateReader.h
Expand Down Expand Up @@ -369,6 +371,8 @@ SET(SRC_FLAMEGPU
${FLAMEGPU_ROOT}/src/flamegpu/runtime/environment/HostEnvironment.cu
${FLAMEGPU_ROOT}/src/flamegpu/runtime/environment/HostEnvironmentDirectedGraph.cu
${FLAMEGPU_ROOT}/src/flamegpu/runtime/random/HostRandom.cu
${FLAMEGPU_ROOT}/src/flamegpu/io/JSONRunPlanReader.cpp
${FLAMEGPU_ROOT}/src/flamegpu/io/JSONRunPlanWriter.cpp
${FLAMEGPU_ROOT}/src/flamegpu/io/JSONStateReader.cu
${FLAMEGPU_ROOT}/src/flamegpu/io/JSONStateWriter.cu
${FLAMEGPU_ROOT}/src/flamegpu/io/StateReader.cu
Expand Down
235 changes: 235 additions & 0 deletions src/flamegpu/io/JSONRunPlanReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#include "flamegpu/io/JSONRunPlanReader.h"

#include <fstream>
#include <stack>

#include <rapidjson/stream.h>

Check failure on line 6 in src/flamegpu/io/JSONRunPlanReader.cpp

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Found C system header after C++ system header. Should be: JSONRunPlanReader.h, c system, c++ system, other.
#include <rapidjson/reader.h>

Check failure on line 7 in src/flamegpu/io/JSONRunPlanReader.cpp

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Found C system header after C++ system header. Should be: JSONRunPlanReader.h, c system, c++ system, other.
#include <rapidjson/error/en.h>

Check failure on line 8 in src/flamegpu/io/JSONRunPlanReader.cpp

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Found C system header after C++ system header. Should be: JSONRunPlanReader.h, c system, c++ system, other.

#include "flamegpu/model/ModelDescription.h"

namespace flamegpu {
namespace io {
class JSONRunPlanReader_impl : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>, JSONRunPlanReader_impl> {
enum Mode { Root, Plan, Core, Properties, PropertyArray, Nop };
std::stack<Mode> mode;
std::string lastKey;
/**
* Tracks current position reading environment property arrays
*/
unsigned int current_array_index;
std::string filename;
RunPlanVector &rpv;

public:
JSONRunPlanReader_impl(const std::string& _filename, RunPlanVector& _rpv)
: filename(_filename)
, rpv(_rpv) { }
template<typename T>
bool processValue(const T val) {
Mode isArray = Nop;
if (mode.top() == PropertyArray) {
isArray = mode.top();
mode.pop();
}
if (mode.top() == Properties) {
const auto it = rpv.environment->find(lastKey);
if (it == rpv.environment->end()) {
THROW exception::RapidJSONError("Input file contains unrecognised environment property '%s',"
"in JSONRunPlanReader::load()\n", lastKey.c_str());
}
if (current_array_index >= it->second.data.elements) {
THROW exception::RapidJSONError("Input file contains environment property '%s' with %u elements expected %u,"
"in JSONRunPlanReader::load()\n", lastKey.c_str(), current_array_index, it->second.data.elements);
}
// Retrieve the linked any and replace the value
const auto rp = rpv.end();
const std::type_index val_type = it->second.data.type;
if (it->second.data.elements ==0) {
// Properties don't exist by default, so must be created
if (val_type == std::type_index(typeid(float))) {
rp->setProperty(lastKey, static_cast<float>(val));
} else if (val_type == std::type_index(typeid(double))) {
rp->setProperty(lastKey, static_cast<double>(val));
} else if (val_type == std::type_index(typeid(int64_t))) {
rp->setProperty(lastKey, static_cast<int64_t>(val));
} else if (val_type == std::type_index(typeid(uint64_t))) {
rp->setProperty(lastKey, static_cast<uint64_t>(val));
} else if (val_type == std::type_index(typeid(int32_t))) {
rp->setProperty(lastKey, static_cast<int32_t>(val));
} else if (val_type == std::type_index(typeid(uint32_t))) {
rp->setProperty(lastKey, static_cast<uint32_t>(val));
} else if (val_type == std::type_index(typeid(int16_t))) {
rp->setProperty(lastKey, static_cast<int16_t>(val));
} else if (val_type == std::type_index(typeid(uint16_t))) {
rp->setProperty(lastKey, static_cast<uint16_t>(val));
} else if (val_type == std::type_index(typeid(int8_t))) {
rp->setProperty(lastKey, static_cast<int8_t>(val));
} else if (val_type == std::type_index(typeid(uint8_t))) {
rp->setProperty(lastKey, static_cast<uint8_t>(val));
} else {
THROW exception::RapidJSONError("RunPlan contains property '%s' of unsupported type '%s', "
"in JSONRunPlanReader::load()\n", lastKey.c_str(), val_type.name());
}
} else {
// Arrays require more fiddly handling
// Create the array if this is the first item
if (current_array_index == 0) {
rp->property_overrides.emplace(lastKey, detail::Any(it->second));
}
// Copy in the specific value
const auto prop_it = rp->property_overrides.at(lastKey);
if (val_type == std::type_index(typeid(float))) {
static_cast<double*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<float>(val);
} else if (val_type == std::type_index(typeid(double))) {
static_cast<double*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<double>(val);
} else if (val_type == std::type_index(typeid(int64_t))) {
static_cast<int64_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<int64_t>(val);
} else if (val_type == std::type_index(typeid(uint64_t))) {
static_cast<uint64_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<uint64_t>(val);
} else if (val_type == std::type_index(typeid(int32_t))) {
static_cast<int32_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<int32_t>(val);
} else if (val_type == std::type_index(typeid(uint32_t))) {
static_cast<uint32_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<uint32_t>(val);
} else if (val_type == std::type_index(typeid(int16_t))) {
static_cast<int16_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<int16_t>(val);
} else if (val_type == std::type_index(typeid(uint16_t))) {
static_cast<uint16_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<uint16_t>(val);
} else if (val_type == std::type_index(typeid(int8_t))) {
static_cast<int8_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<int8_t>(val);
} else if (val_type == std::type_index(typeid(uint8_t))) {
static_cast<uint8_t*>(const_cast<void*>(prop_it.ptr))[current_array_index++] = static_cast<uint8_t>(val);
} else {
THROW exception::RapidJSONError("RunPlan contains property '%s' of unsupported type '%s', "
"in JSONRunPlanReader::load()\n", lastKey.c_str(), val_type.name());
}
}
} else {
THROW exception::RapidJSONError("Unexpected value whilst parsing input file '%s'.\n", filename.c_str());
}
if (isArray == PropertyArray) {
mode.push(isArray);
}
return true;
}
bool Null() { return true; }
bool Bool(bool b) { return processValue<bool>(b); }
bool Int(int i) { return processValue<int32_t>(i); }
bool Uint(unsigned u) {
if (mode.top() == Plan) {
if (lastKey == "steps") {
rpv.end()->setSteps(u);
return true;
}
return false;
}
return processValue<uint32_t>(u);
}
bool Int64(int64_t i) { return processValue<int64_t>(i); }
bool Uint64(uint64_t u) {
if (mode.top() == Plan) {
if (lastKey == "random_seed") {
rpv.end()->setRandomSimulationSeed(u);
return true;
}
return false;
}
return processValue<uint64_t>(u);
}
bool Double(double d) { return processValue<double>(d); }
bool String(const char*s, rapidjson::SizeType, bool) {
if (mode.top() == Plan) {
if (lastKey == "output_subdirectory") {
rpv.end()->setOutputSubdirectory(s);
return true;
}
}
// Properties never contain strings
THROW exception::RapidJSONError("Unexpected string whilst parsing input file '%s'.\n", filename.c_str());
}
bool StartObject() {
if (mode.empty()) {
mode.push(Root);
} else if (mode.top() == Plan) {
if (lastKey == "RunPlanVector") {
mode.push(Core);
} else {
THROW exception::RapidJSONError("Unexpected object start whilst parsing input file '%s'.\n", filename.c_str());
}
} else if (mode.top() == Core) {
if (lastKey == "properties") {
mode.push(Properties);
} else {
THROW exception::RapidJSONError("Unexpected object start whilst parsing input file '%s'.\n", filename.c_str());
}
} else if (mode.top() == PropertyArray) {
rpv.push_back(RunPlan(rpv.environment, rpv.allow_0_steps));
} else {
THROW exception::RapidJSONError("Unexpected object start whilst parsing input file '%s'.\n", filename.c_str());
}
return true;
}
bool Key(const char* str, rapidjson::SizeType, bool) {
lastKey = str;
return true;
}
bool EndObject(rapidjson::SizeType) {
mode.pop();
return true;
}
bool StartArray() {
if (current_array_index != 0) {
THROW exception::RapidJSONError("Array start when current_array_index !=0, in file '%s'. This should never happen.\n", filename.c_str());
}
if (mode.top() == Plan && lastKey == "properties") {
mode.push(Properties);
} else if (mode.top() == Properties) {
mode.push(PropertyArray);
} else {
THROW exception::RapidJSONError("Unexpected array start whilst parsing input file '%s'.\n", filename.c_str());
}
return true;
}
bool EndArray(rapidjson::SizeType) {
if (mode.top() == PropertyArray) {
mode.pop();
if (mode.top() == Properties) {
// Confirm env array had correct number of elements
const auto &prop = rpv.environment->at(lastKey);
if (current_array_index != prop.data.elements) {
THROW exception::RapidJSONError("Input file contains property '%s' with %u elements expected %u,"
"in JSONRunPlanReader::load()\n", lastKey.c_str(), current_array_index, prop.data.elements);
}
}
current_array_index = 0;
} else if (mode.top() == Properties) {
mode.pop();
} else {
THROW exception::RapidJSONError("Unexpected array end whilst parsing input file '%s'.\n", filename.c_str());
}
return true;
}
};
RunPlanVector JSONRunPlanReader::load(const std::string &input_filepath, const ModelDescription& model) {
// Read the input file into a stringstream
std::ifstream in(input_filepath, std::ios::in | std::ios::binary);
if (!in.is_open()) {
THROW exception::InvalidFilePath("Unable to open file '%s' for reading, in JSONRunPlanReader::load().", input_filepath.c_str());
}
const std::string filestring = std::string((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
rapidjson::StringStream filess = rapidjson::StringStream(filestring.c_str());
in.close();
// Attempt to parse the JSON into a RunPlanVector
RunPlanVector result(model.model, 0);
rapidjson::Reader reader;
JSONRunPlanReader_impl handler(input_filepath, result);
rapidjson::ParseResult pr = reader.Parse<rapidjson::kParseNanAndInfFlag, rapidjson::StringStream, flamegpu::io::JSONRunPlanReader_impl>(filess, handler);
if (pr.Code() != rapidjson::ParseErrorCode::kParseErrorNone) {
THROW exception::RapidJSONError("Whilst parsing input file '%s', RapidJSON returned error: %s\n", input_filepath.c_str(), rapidjson::GetParseError_En(pr.Code()));
}
// Return the result
return result;
}
}

Check failure on line 234 in src/flamegpu/io/JSONRunPlanReader.cpp

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Namespace should be terminated with "// namespace io"
}

Check failure on line 235 in src/flamegpu/io/JSONRunPlanReader.cpp

View workflow job for this annotation

GitHub Actions / cpplint (11.8, ubuntu-20.04)

Namespace should be terminated with "// namespace flamegpu"
Loading

0 comments on commit 48768e8

Please sign in to comment.