Improve the Settings serialization API, expose to JS.
This not only makes it easier to expose to JavaScript, it also paves the way for a much more efficient implementation.
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
#ifndef VIEWER_REMOTE_SERVER_H
|
||||
#define VIEWER_REMOTE_SERVER_H
|
||||
|
||||
#include <viewer/Settings.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <mutex>
|
||||
|
||||
@@ -25,7 +27,8 @@ class CivetServer;
|
||||
namespace filament {
|
||||
namespace viewer {
|
||||
|
||||
class WsHandler;
|
||||
class MessageSender;
|
||||
class MessageReceiver;
|
||||
|
||||
/**
|
||||
* Encapsulates a message sent from the web client.
|
||||
@@ -54,24 +57,27 @@ class RemoteServer {
|
||||
public:
|
||||
RemoteServer(int port = 8082);
|
||||
~RemoteServer();
|
||||
bool isValid() const { return mCivetServer; }
|
||||
bool isValid() const { return mMessageSender; }
|
||||
char const* peekIncomingLabel() const;
|
||||
ReceivedMessage const* peekReceivedMessage() const;
|
||||
ReceivedMessage const* acquireReceivedMessage();
|
||||
void releaseReceivedMessage(ReceivedMessage const* message);
|
||||
void sendMessage(const Settings& settings);
|
||||
void sendMessage(const char* label, const char* buffer, size_t bufsize);
|
||||
|
||||
private:
|
||||
void enqueueReceivedMessage(ReceivedMessage* message);
|
||||
void setIncomingMessage(ReceivedMessage* message);
|
||||
CivetServer* mCivetServer = nullptr;
|
||||
WsHandler* mWsHandler = nullptr;
|
||||
MessageSender* mMessageSender = nullptr;
|
||||
MessageReceiver* mMessageReceiver = nullptr;
|
||||
size_t mNextMessageUid = 0;
|
||||
size_t mOldestMessageUid = 0;
|
||||
static const size_t kMessageCapacity = 4;
|
||||
ReceivedMessage* mReceivedMessages[kMessageCapacity] = {};
|
||||
ReceivedMessage* mIncomingMessage = nullptr;
|
||||
JsonSerializer mSerializer;
|
||||
mutable std::mutex mReceivedMessagesMutex;
|
||||
friend class WsHandler;
|
||||
friend class MessageReceiver;
|
||||
};
|
||||
|
||||
} // namespace viewer
|
||||
|
||||
@@ -51,12 +51,6 @@ using ToneMapping = filament::ColorGrading::ToneMapping;
|
||||
using VignetteOptions = filament::View::VignetteOptions;
|
||||
using VsmShadowOptions = filament::View::VsmShadowOptions;
|
||||
|
||||
// Reads the given JSON blob and updates the corresponding fields in the given Settings object.
|
||||
// - The given JSON blob need not specify all settings.
|
||||
// - Returns true if successful.
|
||||
// - This function writes warnings and error messages into the utils log.
|
||||
bool readJson(const char* jsonChunk, size_t size, Settings* out);
|
||||
|
||||
// These functions push all editable property values to their respective Filament objects.
|
||||
void applySettings(const ViewSettings& settings, View* dest);
|
||||
void applySettings(const MaterialSettings& settings, MaterialInstance* dest);
|
||||
@@ -64,19 +58,24 @@ void applySettings(const MaterialSettings& settings, MaterialInstance* dest);
|
||||
// Creates a new ColorGrading object based on the given settings.
|
||||
ColorGrading* createColorGrading(const ColorGradingSettings& settings, Engine* engine);
|
||||
|
||||
// Generates human-readable JSON strings from settings objects.
|
||||
std::string writeJson(const AmbientOcclusionOptions& in);
|
||||
std::string writeJson(const BloomOptions& in);
|
||||
std::string writeJson(const ColorGradingSettings& in);
|
||||
std::string writeJson(const DepthOfFieldOptions& in);
|
||||
std::string writeJson(const DynamicLightingSettings& in);
|
||||
std::string writeJson(const FogOptions& in);
|
||||
std::string writeJson(const MaterialSettings& in);
|
||||
std::string writeJson(const RenderQuality& in);
|
||||
std::string writeJson(const Settings& in);
|
||||
std::string writeJson(const TemporalAntiAliasingOptions& in);
|
||||
std::string writeJson(const ViewSettings& in);
|
||||
std::string writeJson(const VignetteOptions& in);
|
||||
class JsonSerializer {
|
||||
public:
|
||||
JsonSerializer();
|
||||
~JsonSerializer();
|
||||
|
||||
// Writes a human-readable JSON string into an internal buffer and returns the result.
|
||||
const std::string& writeJson(const Settings& in);
|
||||
|
||||
// Reads the given JSON blob and updates the corresponding fields in the given Settings object.
|
||||
// - The given JSON blob need not specify all settings.
|
||||
// - Returns true if successful.
|
||||
// - This function writes warnings and error messages into the utils log.
|
||||
bool readJson(const char* jsonChunk, size_t size, Settings* out);
|
||||
|
||||
private:
|
||||
struct Context;
|
||||
Context* context;
|
||||
};
|
||||
|
||||
struct ColorGradingSettings {
|
||||
bool enabled = true;
|
||||
|
||||
@@ -122,7 +122,8 @@ void AutomationEngine::terminate() {
|
||||
}
|
||||
|
||||
void AutomationEngine::exportSettings(const Settings& settings, const char* filename) {
|
||||
std::string contents = writeJson(settings);
|
||||
JsonSerializer serializer;
|
||||
std::string contents = serializer.writeJson(settings);
|
||||
std::ofstream out(filename);
|
||||
if (!out) {
|
||||
gStatus = "Failed to export settings file.";
|
||||
|
||||
@@ -104,7 +104,8 @@ static int parseBaseSettings(jsmntok_t const* tokens, int i, const char* jsonChu
|
||||
}
|
||||
|
||||
// Now that we have a complete JSON string, apply this property change.
|
||||
readJson(json.c_str(), json.size(), out);
|
||||
JsonSerializer serializer;
|
||||
serializer.readJson(json.c_str(), json.size(), out);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
@@ -195,6 +196,7 @@ static int parseAutomationSpec(jsmntok_t const* tokens, int i, const char* jsonC
|
||||
}
|
||||
|
||||
size_t caseIndex = 0;
|
||||
JsonSerializer serializer;
|
||||
while (true) {
|
||||
if (VERBOSE) {
|
||||
slog.i << " Generating test case " << caseIndex << io::endl;
|
||||
@@ -228,7 +230,7 @@ static int parseAutomationSpec(jsmntok_t const* tokens, int i, const char* jsonC
|
||||
if (VERBOSE) {
|
||||
slog.i << " Applying " << jsonString.c_str() << io::endl;
|
||||
}
|
||||
if (!readJson(jsonString.c_str(), jsonString.size(), &testCase)) {
|
||||
if (!serializer.readJson(jsonString.c_str(), jsonString.size(), &testCase)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,16 @@ using namespace utils;
|
||||
namespace filament {
|
||||
namespace viewer {
|
||||
|
||||
class WsHandler : public CivetWebSocketHandler {
|
||||
class MessageSender : public CivetServer {
|
||||
public:
|
||||
MessageSender(const char** options) : CivetServer(options) {}
|
||||
void sendMessage(const char* label, const char* buffer, size_t bufsize);
|
||||
};
|
||||
|
||||
class MessageReceiver : public CivetWebSocketHandler {
|
||||
public:
|
||||
WsHandler(RemoteServer* server) : mServer(server) {}
|
||||
~WsHandler() { delete mReceivedMessage; }
|
||||
MessageReceiver(RemoteServer* server) : mServer(server) {}
|
||||
~MessageReceiver() { delete mReceivedMessage; }
|
||||
bool handleData(CivetServer* server, struct mg_connection*, int, char* , size_t) override;
|
||||
private:
|
||||
RemoteServer* mServer;
|
||||
@@ -47,22 +53,22 @@ RemoteServer::RemoteServer(int port) {
|
||||
};
|
||||
std::string portString = std::to_string(port);
|
||||
kServerOptions[1] = portString.c_str();
|
||||
mCivetServer = new CivetServer(kServerOptions);
|
||||
if (!mCivetServer->getContext()) {
|
||||
mMessageSender = new MessageSender(kServerOptions);
|
||||
if (!mMessageSender->getContext()) {
|
||||
slog.e << "Unable to start RemoteServer, see civetweb.txt for details." << io::endl;
|
||||
delete mCivetServer;
|
||||
mCivetServer = nullptr;
|
||||
mWsHandler = nullptr;
|
||||
delete mMessageSender;
|
||||
mMessageSender = nullptr;
|
||||
mMessageReceiver = nullptr;
|
||||
return;
|
||||
}
|
||||
mWsHandler = new WsHandler(this);
|
||||
mCivetServer->addWebSocketHandler("", mWsHandler);
|
||||
mMessageReceiver = new MessageReceiver(this);
|
||||
mMessageSender->addWebSocketHandler("", mMessageReceiver);
|
||||
slog.i << "RemoteServer listening at ws://localhost:" << port << io::endl;
|
||||
}
|
||||
|
||||
RemoteServer::~RemoteServer() {
|
||||
delete mCivetServer;
|
||||
delete mWsHandler;
|
||||
delete mMessageSender;
|
||||
delete mMessageReceiver;
|
||||
for (auto msg : mReceivedMessages) {
|
||||
releaseReceivedMessage(msg);
|
||||
}
|
||||
@@ -122,8 +128,17 @@ void RemoteServer::releaseReceivedMessage(ReceivedMessage const* message) {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteServer::sendMessage(const Settings& settings) {
|
||||
const auto& json = mSerializer.writeJson(settings);
|
||||
mMessageSender->sendMessage("settings.json", json.c_str(), json.size() + 1);
|
||||
}
|
||||
|
||||
void RemoteServer::sendMessage(const char* label, const char* buffer, size_t bufsize) {
|
||||
mMessageSender->sendMessage(label, buffer, bufsize);
|
||||
}
|
||||
|
||||
// NOTE: This is invoked off the main thread.
|
||||
bool WsHandler::handleData(CivetServer* server, struct mg_connection* conn, int bits,
|
||||
bool MessageReceiver::handleData(CivetServer* server, struct mg_connection* conn, int bits,
|
||||
char* data, size_t size) {
|
||||
const bool final = bits & 0x80;
|
||||
const int opcode = bits & 0xf;
|
||||
@@ -162,5 +177,12 @@ bool WsHandler::handleData(CivetServer* server, struct mg_connection* conn, int
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageSender::sendMessage(const char* label, const char* buffer, size_t bufsize) {
|
||||
for (auto iter : connections) {
|
||||
mg_websocket_write(iter.first, 0x80, label, strlen(label) + 1);
|
||||
mg_websocket_write(iter.first, 0x80, buffer, bufsize);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace viewer
|
||||
} // namespace filament
|
||||
|
||||
@@ -30,6 +30,19 @@ using namespace utils;
|
||||
namespace filament {
|
||||
namespace viewer {
|
||||
|
||||
static std::string writeJson(const Settings& in);
|
||||
static std::string writeJson(const AmbientOcclusionOptions& in);
|
||||
static std::string writeJson(const BloomOptions& in);
|
||||
static std::string writeJson(const ColorGradingSettings& in);
|
||||
static std::string writeJson(const DepthOfFieldOptions& in);
|
||||
static std::string writeJson(const DynamicLightingSettings& in);
|
||||
static std::string writeJson(const FogOptions& in);
|
||||
static std::string writeJson(const MaterialSettings& in);
|
||||
static std::string writeJson(const RenderQuality& in);
|
||||
static std::string writeJson(const TemporalAntiAliasingOptions& in);
|
||||
static std::string writeJson(const ViewSettings& in);
|
||||
static std::string writeJson(const VignetteOptions& in);
|
||||
|
||||
// Compares a JSON string token against a C string.
|
||||
int compare(jsmntok_t tok, const char* jsonChunk, const char* str) {
|
||||
size_t slen = strlen(str);
|
||||
@@ -669,32 +682,6 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Settings* out)
|
||||
return i;
|
||||
}
|
||||
|
||||
bool readJson(const char* jsonChunk, size_t size, Settings* out) {
|
||||
jsmn_parser parser = { 0, 0, 0 };
|
||||
|
||||
int tokenCount = jsmn_parse(&parser, jsonChunk, size, nullptr, 0);
|
||||
if (tokenCount <= 0) {
|
||||
slog.e << "Badly formed JSON." << io::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
jsmntok_t* tokens = (jsmntok_t*) malloc(sizeof(jsmntok_t) * tokenCount);
|
||||
assert(tokens);
|
||||
|
||||
jsmn_init(&parser);
|
||||
tokenCount = jsmn_parse(&parser, jsonChunk, size, tokens, tokenCount);
|
||||
|
||||
if (tokenCount <= 0) {
|
||||
free(tokens);
|
||||
slog.e << "Badly formed JSON." << io::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int i = parse(tokens, 0, jsonChunk, out);
|
||||
free(tokens);
|
||||
return i >= 0;
|
||||
}
|
||||
|
||||
void applySettings(const ViewSettings& settings, View* dest) {
|
||||
dest->setSampleCount(settings.sampleCount);
|
||||
dest->setAntiAliasing(settings.antiAliasing);
|
||||
@@ -1089,5 +1076,50 @@ bool ColorGradingSettings::operator==(const ColorGradingSettings &rhs) const {
|
||||
scale == rhs.scale;
|
||||
}
|
||||
|
||||
// TODO: This can be made faster by ditching ostringstream and using snprintf.
|
||||
// At the very least, we should stash the ostringstream here in the context object and re-use it.
|
||||
struct JsonSerializer::Context {
|
||||
std::string buffer;
|
||||
};
|
||||
|
||||
JsonSerializer::JsonSerializer() {
|
||||
context = new JsonSerializer::Context();
|
||||
}
|
||||
|
||||
JsonSerializer::~JsonSerializer() {
|
||||
delete context;
|
||||
}
|
||||
|
||||
const std::string& JsonSerializer::writeJson(const Settings& in) {
|
||||
context->buffer = viewer::writeJson(in);
|
||||
return context->buffer;
|
||||
}
|
||||
|
||||
bool JsonSerializer::readJson(const char* jsonChunk, size_t size, Settings* out) {
|
||||
jsmn_parser parser = { 0, 0, 0 };
|
||||
|
||||
int tokenCount = jsmn_parse(&parser, jsonChunk, size, nullptr, 0);
|
||||
if (tokenCount <= 0) {
|
||||
slog.e << "Badly formed JSON." << io::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
jsmntok_t* tokens = (jsmntok_t*) malloc(sizeof(jsmntok_t) * tokenCount);
|
||||
assert(tokens);
|
||||
|
||||
jsmn_init(&parser);
|
||||
tokenCount = jsmn_parse(&parser, jsonChunk, size, tokens, tokenCount);
|
||||
|
||||
if (tokenCount <= 0) {
|
||||
free(tokens);
|
||||
slog.e << "Badly formed JSON." << io::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int i = parse(tokens, 0, jsonChunk, out);
|
||||
free(tokens);
|
||||
return i >= 0;
|
||||
}
|
||||
|
||||
} // namespace viewer
|
||||
} // namespace filament
|
||||
|
||||
@@ -148,24 +148,26 @@ static const char* JSON_TEST_AUTOMATION = R"TXT([{
|
||||
}])TXT";
|
||||
|
||||
TEST_F(ViewSettingsTest, JsonTestDefaults) {
|
||||
JsonSerializer serializer;
|
||||
Settings settings1 = {0};
|
||||
ASSERT_TRUE(readJson(JSON_TEST_DEFAULTS, strlen(JSON_TEST_DEFAULTS), &settings1));
|
||||
ASSERT_TRUE(serializer.readJson(JSON_TEST_DEFAULTS, strlen(JSON_TEST_DEFAULTS), &settings1));
|
||||
|
||||
ASSERT_TRUE(settings1.view.bloom.threshold);
|
||||
|
||||
Settings settings2;
|
||||
ASSERT_TRUE(readJson("{}", strlen("{}"), &settings2));
|
||||
ASSERT_FALSE(readJson("{ badly_formed }", strlen("{ badly_formed }"), &settings2));
|
||||
ASSERT_TRUE(serializer.readJson("{}", strlen("{}"), &settings2));
|
||||
ASSERT_FALSE(serializer.readJson("{ badly_formed }", strlen("{ badly_formed }"), &settings2));
|
||||
|
||||
Settings settings3;
|
||||
ASSERT_EQ(writeJson(settings2), writeJson(settings3));
|
||||
ASSERT_EQ(serializer.writeJson(settings2), serializer.writeJson(settings3));
|
||||
}
|
||||
|
||||
TEST_F(ViewSettingsTest, JsonTestMaterial) {
|
||||
JsonSerializer serializer;
|
||||
Settings settings = {0};
|
||||
std::string js = "{" + std::string(JSON_TEST_MATERIAL) + "}";
|
||||
ASSERT_TRUE(readJson(js.c_str(), js.size(), &settings));
|
||||
std::string serialized = writeJson(settings);
|
||||
ASSERT_TRUE(serializer.readJson(js.c_str(), js.size(), &settings));
|
||||
std::string serialized = serializer.writeJson(settings);
|
||||
ASSERT_PRED_FORMAT2(testing::IsSubstring, "\"baz\": [1, 2, 3]", serialized);
|
||||
}
|
||||
|
||||
|
||||
@@ -252,7 +252,8 @@ static bool loadSettings(const char* filename, Settings* out) {
|
||||
if (!in.read(json.data(), contentSize)) {
|
||||
return false;
|
||||
}
|
||||
return readJson(json.data(), contentSize, out);
|
||||
JsonSerializer serializer;
|
||||
return serializer.readJson(json.data(), contentSize, out);
|
||||
}
|
||||
|
||||
static void createGroundPlane(Engine* engine, Scene* scene, App& app) {
|
||||
|
||||
@@ -1830,6 +1830,10 @@ class_<ResourceLoader>("gltfio$ResourceLoader")
|
||||
|
||||
class_<Settings>("Settings");
|
||||
|
||||
class_<JsonSerializer>("JsonSerializer")
|
||||
.constructor<>()
|
||||
.function("writeJson", &JsonSerializer::writeJson);
|
||||
|
||||
class_<SimpleViewer>("SimpleViewer")
|
||||
.constructor<Engine*, Scene*, View*, int>()
|
||||
.function("renderUserInterface", &SimpleViewer::renderUserInterface, allow_raw_pointers())
|
||||
|
||||
@@ -130,6 +130,8 @@ class App {
|
||||
this.renderer = engine.createRenderer();
|
||||
this.renderer.setClearOptions({clearColor: kBackgroundColor, clear: true});
|
||||
this.camera = engine.createCamera(Filament.EntityManager.get().create());
|
||||
this.serializer = new Filament.JsonSerializer();
|
||||
this.previousSettingsJson = "";
|
||||
|
||||
view.setScene(scene);
|
||||
view.setCamera(this.camera);
|
||||
@@ -150,8 +152,6 @@ class App {
|
||||
|
||||
Object.seal(this);
|
||||
|
||||
const settings = this.simpleViewer.getSettings(); // TODO: do something with this
|
||||
|
||||
this.canvas.addEventListener("pointermove", e => {
|
||||
this.mouseX = e.offsetX * window.devicePixelRatio;
|
||||
this.mouseY = e.offsetY * window.devicePixelRatio;
|
||||
@@ -266,8 +266,17 @@ class App {
|
||||
if (this.mouseButtonEvents.length > 0) {
|
||||
this.mouseButton = this.mouseButtonEvents.shift();
|
||||
}
|
||||
|
||||
this.simpleViewer.renderUserInterface(dt, this.uiview, window.devicePixelRatio,
|
||||
this.mouseX, this.mouseY, this.mouseButton, this.mouseWheelY);
|
||||
|
||||
const settingsJson = this.serializer.writeJson(this.simpleViewer.getSettings()).slice();
|
||||
|
||||
if (this.previousSettingsJson != settingsJson) {
|
||||
console.info("Settings have changed.");
|
||||
this.previousSettingsJson = settingsJson;
|
||||
}
|
||||
|
||||
this.mouseWheelY = 0;
|
||||
|
||||
this.renderer.beginFrame(this.swapChain);
|
||||
|
||||
Reference in New Issue
Block a user