Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions indra/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ include(PlutoSVG)
include(PNG)
include(SDL3)
include(Sentry)
include(simdjson)
include(simdutf)
include(SSE2NEON)
include(TinyEXR)
Expand Down
4 changes: 2 additions & 2 deletions indra/cmake/Boost.cmake
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
include_guard()
add_library(ll::boost INTERFACE IMPORTED)

find_package(Boost CONFIG REQUIRED COMPONENTS context dll fiber filesystem json program_options url)
target_link_libraries(ll::boost INTERFACE Boost::disable_autolinking Boost::headers Boost::dll Boost::fiber Boost::context Boost::filesystem Boost::program_options Boost::url Boost::json)
find_package(Boost CONFIG REQUIRED COMPONENTS context dll fiber filesystem program_options url)
target_link_libraries(ll::boost INTERFACE Boost::disable_autolinking Boost::headers Boost::dll Boost::fiber Boost::context Boost::filesystem Boost::program_options Boost::url)

if(WINDOWS)
find_package(Boost CONFIG REQUIRED COMPONENTS stacktrace_windbg)
Expand Down
6 changes: 6 additions & 0 deletions indra/cmake/simdjson.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include_guard()

find_package(simdjson CONFIG REQUIRED)

add_library(ll::simdjson INTERFACE IMPORTED)
target_link_libraries(ll::simdjson INTERFACE simdjson::simdjson)
2 changes: 2 additions & 0 deletions indra/llcommon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ target_link_libraries(
ll::tracy
ll::sse2neon
ll::xxhash
ll::simdjson
ll::simdutf
ll::fmt
Threads::Threads
Expand Down Expand Up @@ -331,6 +332,7 @@ if (BUILD_TESTING)
LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}" "${test_project}")
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}" "${test_project}")
LL_ADD_INTEGRATION_TEST(llsd "" "${test_libs}" "${test_project}")
LL_ADD_INTEGRATION_TEST(llsdjson "" "${test_libs}" "${test_project}")
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}" "${test_project}")
LL_ADD_INTEGRATION_TEST(llsdutil "" "llcommon;llmath" "${test_project}")
LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}" "${test_project}")
Expand Down
172 changes: 138 additions & 34 deletions indra/llcommon/llsdjson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,99 +33,191 @@
#include "llsdutil.h"
#include "llerror.h"

#include <cmath>
#include <cstdlib>

//=========================================================================
LLSD LlsdFromJson(const boost::json::value& val)
LLSD LlsdFromJson(const simdjson::dom::element& val)
{
LLSD result;

switch (val.kind())
switch (val.type())
{
default:
case boost::json::kind::null:
case simdjson::dom::element_type::NULL_VALUE:
break;
case simdjson::dom::element_type::INT64:
result = LLSD(val.get_int64().value_unsafe());
break;
case boost::json::kind::int64:
case boost::json::kind::uint64:
result = LLSD(val.to_number<int64_t>());
case simdjson::dom::element_type::UINT64:
result = LLSD(val.get_uint64().value_unsafe());
break;
case boost::json::kind::double_:
result = LLSD(val.to_number<double>());
case simdjson::dom::element_type::DOUBLE:
result = LLSD(val.get_double().value_unsafe());
break;
case simdjson::dom::element_type::BIGINT:
{
// integer too large for int64/uint64: degrade to Real, matching how
// such literals previously parsed as doubles. Only reachable if the
// parser enables bigint storage; the default configuration fails the
// parse with BIGINT_ERROR instead.
double bigval;
if (val.get_double().get(bigval) != simdjson::SUCCESS)
{
std::string_view digits;
if (val.get_bigint().get(digits) != simdjson::SUCCESS)
{
break;
}
// saturates to +/-HUGE_VAL rather than failing
bigval = strtod(std::string(digits).c_str(), nullptr);
}
result = LLSD(bigval);
break;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
case boost::json::kind::string:
result = LLSD(boost::json::value_to<std::string>(val));
}
case simdjson::dom::element_type::STRING:
result = LLSD(std::string(val.get_string().value_unsafe()));
break;
case boost::json::kind::bool_:
result = LLSD(val.as_bool());
case simdjson::dom::element_type::BOOL:
result = LLSD(val.get_bool().value_unsafe());
break;
case boost::json::kind::array:
case simdjson::dom::element_type::ARRAY:
{
result = LLSD::emptyArray();
const boost::json::array& array = val.as_array();
simdjson::dom::array array = val.get_array().value_unsafe();
size_t size = array.size();
// allocate elements 0 .. (size() - 1) to avoid incremental allocation
if (!array.empty())
if (size > 0)
{
result[size - 1] = LLSD();
}
for (size_t i = 0; i < size; i++)
size_t i = 0;
for (const simdjson::dom::element& element : array)
{
result[i] = (LlsdFromJson(array[i]));
result[i++] = LlsdFromJson(element);
}
break;
}
case boost::json::kind::object:
case simdjson::dom::element_type::OBJECT:
{
result = LLSD::emptyMap();
for (const auto& element : val.as_object())
// copy the handle out first: iterating the simdjson_result temporary
// dangles, as range-for does not extend the inner temporary's lifetime
simdjson::dom::object object = val.get_object().value_unsafe();
for (const simdjson::dom::key_value_pair& member : object)
{
result[std::string_view(element.key())] = LlsdFromJson(element.value());
result[member.key] = LlsdFromJson(member.value);
}
break;
}
}
return result;
}

//=========================================================================
boost::json::value LlsdToJson(const LLSD &val)
// parser reuse avoids reallocating its internal buffers on every call; the
// converted LLSD owns all its data, so nothing references the parser once
// the conversion returns
static simdjson::dom::parser& json_parser()
{
thread_local simdjson::dom::parser parser;
return parser;
}

static bool llsd_from_parsed(simdjson::simdjson_result<simdjson::dom::element> parsed,
LLSD& out, std::string* errmsg)
{
simdjson::dom::element root;
simdjson::error_code err = parsed.get(root);
if (err != simdjson::SUCCESS)
{
if (errmsg)
{
*errmsg = simdjson::error_message(err);
}
out = LLSD();
return false;
}
out = LlsdFromJson(root);
return true;
}

bool LlsdFromJsonString(std::string_view json, LLSD& out, std::string* errmsg)
{
boost::json::value result;
return llsd_from_parsed(json_parser().parse(json.data(), json.size()), out, errmsg);
}

bool LlsdFromJsonString(const simdjson::padded_string& json, LLSD& out, std::string* errmsg)
{
return llsd_from_parsed(json_parser().parse(json), out, errmsg);
}

//=========================================================================
static void llsd_to_json(simdjson::builder::string_builder& dst, const LLSD& val)
{
switch (val.type())
{
case LLSD::TypeUndefined:
result = nullptr;
dst.append_null();
break;
case LLSD::TypeBoolean:
result = val.asBoolean();
dst.append(val.asBoolean());
break;
case LLSD::TypeInteger:
result = val.asInteger();
dst.append(val.asInteger());
break;
case LLSD::TypeReal:
result = val.asReal();
{
F64 real = val.asReal();
if (std::isfinite(real))
{
dst.append(real);
}
else
{
// JSON has no representation for NaN or infinities
dst.append_null();
}
break;
}
case LLSD::TypeURI:
case LLSD::TypeDate:
case LLSD::TypeUUID:
case LLSD::TypeString:
result = val.asString();
dst.escape_and_append_with_quotes(val.asString());
break;
case LLSD::TypeMap:
{
boost::json::object& obj = result.emplace_object();
obj.reserve(val.size());
dst.start_object();
bool first = true;
for (const auto& llsd_dat : llsd::inMap(val))
{
obj[llsd_dat.first] = LlsdToJson(llsd_dat.second);
if (!first)
{
dst.append_comma();
}
first = false;
dst.escape_and_append_with_quotes(llsd_dat.first);
dst.append_colon();
llsd_to_json(dst, llsd_dat.second);
}
dst.end_object();
break;
}
case LLSD::TypeArray:
{
boost::json::array& json_array = result.emplace_array();
json_array.reserve(val.size());
dst.start_array();
bool first = true;
for (const auto& llsd_dat : llsd::inArray(val))
{
json_array.push_back(LlsdToJson(llsd_dat));
if (!first)
{
dst.append_comma();
}
first = false;
llsd_to_json(dst, llsd_dat);
}
dst.end_array();
break;
}
case LLSD::TypeBinary:
Expand All @@ -134,6 +226,18 @@ boost::json::value LlsdToJson(const LLSD &val)
<< val.type() << ")." << LL_ENDL;
break;
}
}

return result;
std::string LlsdToJson(const LLSD& val)
{
simdjson::builder::string_builder builder;
llsd_to_json(builder, val);

std::string_view view;
if (builder.view().get(view) != simdjson::SUCCESS)
{
LL_WARNS("LlsdToJson") << "Allocation failure serializing LLSD to JSON" << LL_ENDL;
return std::string();
}
return std::string(view);
}
44 changes: 33 additions & 11 deletions indra/llcommon/llsdjson.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file llsdjson.cpp
* @file llsdjson.h
* @brief LLSD flexible data system
*
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
Expand Down Expand Up @@ -27,17 +27,16 @@
#ifndef LL_LLSDJSON_H
#define LL_LLSDJSON_H

#include <map>
#include <string>
#include <vector>
#include <string_view>

#include "stdtypes.h"

#include "llsd.h"
#include <boost/json.hpp>
#include <simdjson.h>

/// Convert a parsed JSON structure into LLSD maintaining member names and
/// array indexes.
/// Convert a parsed JSON document element into LLSD maintaining member names
/// and array indexes.
/// JSON/JavaScript types are converted as follows:
///
/// JSON Type | LLSD Type
Expand All @@ -53,25 +52,48 @@
///
/// For maps and arrays child entries will be converted and added to the structure.
/// Order is preserved for an array but not for objects.
LLSD LlsdFromJson(const boost::json::value &val);
LLSD LlsdFromJson(const simdjson::dom::element& val);

/// Convert an LLSD object into Parsed JSON object maintaining member names and
/// array indexs.
/// Parse a JSON document from text and convert it into LLSD as above.
/// Returns false (leaving out undefined) on a parse failure; if errmsg is
/// non-null it receives a description of the failure.
bool LlsdFromJsonString(std::string_view json, LLSD& out, std::string* errmsg = nullptr);

/// As above, but parses the buffer in place with no internal copy. Callers
/// that materialize a document from raw bytes should read directly into a
/// simdjson::padded_string and use this overload.
bool LlsdFromJsonString(const simdjson::padded_string& json, LLSD& out, std::string* errmsg = nullptr);

/// disambiguate std::string and C strings against the string_view and
/// padded_string overloads (padded_string converts implicitly from
/// std::string)
inline bool LlsdFromJsonString(const std::string& json, LLSD& out, std::string* errmsg = nullptr)
{
return LlsdFromJsonString(std::string_view(json), out, errmsg);
}

inline bool LlsdFromJsonString(const char* json, LLSD& out, std::string* errmsg = nullptr)
{
return LlsdFromJsonString(std::string_view(json), out, errmsg);
}

/// Serialize an LLSD object into compact JSON text maintaining member names
/// and array indexes.
///
/// Types are converted as follows:
/// LLSD Type | JSON Type
/// --------------+----------------
/// TypeUndefined | null
/// TypeBoolean | boolean
/// TypeInteger | integer
/// TypeReal | real/numeric
/// TypeReal | real/numeric (non-finite values become null)
/// TypeString | string
/// TypeURI | string
/// TypeDate | string
/// TypeUUID | string
/// TypeMap | object
/// TypeArray | array
/// TypeBinary | unsupported
boost::json::value LlsdToJson(const LLSD &val);
std::string LlsdToJson(const LLSD& val);

#endif // LL_LLSDJSON_H
Loading
Loading