From ec134496d5cdf625dcd6ab913cb55e422d3d79ec Mon Sep 17 00:00:00 2001 From: Rye Date: Thu, 11 Jun 2026 21:32:18 -0400 Subject: [PATCH 1/2] Port JSON handling from Boost.JSON to simdjson Replace boost::json across the viewer with simdjson and drop the boost-json vcpkg dependency entirely. - llsdjson: LlsdFromJson converts from simdjson::dom::element; LlsdFromJsonString parses text, with a padded_string overload that parses in place with no internal copy; LlsdToJson serializes LLSD straight to compact JSON text via simdjson's string_builder. Non-finite reals serialize as null; object keys now emit in sorted (LLSD map) order rather than insertion order. - llcorehttputil: JSON response handlers read the body into a padded_string (single full-body copy, no intermediate DOM); postJson/putJson serialize without building a DOM. - lljsonrpcws/llwebsocketmgr: send pre-serialized strings; drop the boost::json::value sendMessage overload. - lltranslate, llfloaterpreference, llappviewerwin32: extract fields directly from simdjson::dom (at_pointer mirrors the old find_pointer paths) with no tree materialization. llvelopack rewrites the release feed as a streaming dom walk + re-emit. - llvoicewebrtc: outgoing data-channel messages built with string_builder helpers; incoming voice data parsed with a reused dom parser. - gltf: Value is now simdjson::dom::element for reads; serialization streams through a new comma-managing JsonWriter over string_builder (LLSD remains forbidden in LL::GLTF); asset extras preserved as minified JSON text; parse failures warn and fail instead of throwing. - llglslshader/llviewerdisplay: manual shader profile dump builds LLSD; 64-bit sample counters stored as Real to avoid S32 overflow. Adds llcommon/tests/llsdjson_test.cpp (13 cases: round-trips, escaping, real precision, non-finite handling, hostile input, padded in-place parse). Adds simdjson to vcpkg.json/cmake and the license notices; removes the json component from Boost.cmake. Co-Authored-By: Claude Fable 5 --- indra/CMakeLists.txt | 1 + indra/cmake/Boost.cmake | 4 +- indra/cmake/simdjson.cmake | 6 + indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/llsdjson.cpp | 157 +++++-- indra/llcommon/llsdjson.h | 44 +- indra/llcommon/tests/llsdjson_test.cpp | 300 +++++++++++++ indra/llcorehttp/lljsonrpcws.cpp | 22 +- indra/llcorehttp/llwebsocketmgr.cpp | 6 - indra/llcorehttp/llwebsocketmgr.h | 3 - indra/llmessage/llcorehttputil.cpp | 35 +- indra/llrender/llglslshader.cpp | 55 +-- indra/llrender/llglslshader.h | 12 +- indra/newview/gltf/README.md | 65 ++- indra/newview/gltf/accessor.cpp | 7 +- indra/newview/gltf/accessor.h | 7 +- indra/newview/gltf/animation.cpp | 16 +- indra/newview/gltf/animation.h | 8 +- indra/newview/gltf/asset.cpp | 84 ++-- indra/newview/gltf/asset.h | 34 +- indra/newview/gltf/buffer_util.h | 473 ++++++++++----------- indra/newview/gltf/common.h | 86 +++- indra/newview/gltf/primitive.cpp | 3 +- indra/newview/gltf/primitive.h | 5 +- indra/newview/licenses-linux.txt | 28 ++ indra/newview/licenses-mac.txt | 28 ++ indra/newview/licenses-win32.txt | 28 ++ indra/newview/llappviewerwin32.cpp | 20 +- indra/newview/llfloaterpreference.cpp | 49 ++- indra/newview/lltranslate.cpp | 170 ++++---- indra/newview/llvelopack.cpp | 103 ++++- indra/newview/llviewerdisplay.cpp | 30 +- indra/newview/llviewermenu.cpp | 1 - indra/newview/llviewerprecompiledheaders.h | 2 +- indra/newview/llvoicewebrtc.cpp | 197 ++++++--- indra/newview/llvoicewebrtc.h | 1 - indra/vcpkg.json | 2 +- 37 files changed, 1381 insertions(+), 713 deletions(-) create mode 100644 indra/cmake/simdjson.cmake create mode 100644 indra/llcommon/tests/llsdjson_test.cpp diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 0a892e01e3f..2142c4280cb 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -281,6 +281,7 @@ include(PlutoSVG) include(PNG) include(SDL3) include(Sentry) +include(simdjson) include(simdutf) include(SSE2NEON) include(TinyEXR) diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index f1faf12fef8..bc2f16553ba 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -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) diff --git a/indra/cmake/simdjson.cmake b/indra/cmake/simdjson.cmake new file mode 100644 index 00000000000..cd327de7b61 --- /dev/null +++ b/indra/cmake/simdjson.cmake @@ -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) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index deac70b570f..5ea1c7f09b0 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -278,6 +278,7 @@ target_link_libraries( ll::tracy ll::sse2neon ll::xxhash + ll::simdjson ll::simdutf ll::fmt Threads::Threads @@ -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}") diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index 57a898e67c2..ce4b1ed185d 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -33,50 +33,66 @@ #include "llsdutil.h" #include "llerror.h" +#include + //========================================================================= -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 boost::json::kind::int64: - case boost::json::kind::uint64: - result = LLSD(val.to_number()); + case simdjson::dom::element_type::INT64: + result = LLSD(val.get_int64().value_unsafe()); break; - case boost::json::kind::double_: - result = LLSD(val.to_number()); + case simdjson::dom::element_type::UINT64: + result = LLSD(val.get_uint64().value_unsafe()); break; - case boost::json::kind::string: - result = LLSD(boost::json::value_to(val)); + case simdjson::dom::element_type::DOUBLE: + result = LLSD(val.get_double().value_unsafe()); break; - case boost::json::kind::bool_: - result = LLSD(val.as_bool()); + case simdjson::dom::element_type::BIGINT: + { + // integer too large for int64/uint64: degrade to Real, matching how + // such literals previously parsed as doubles + double bigval; + if (val.get_double().get(bigval) == simdjson::SUCCESS) + { + result = LLSD(bigval); + } break; - case boost::json::kind::array: + } + case simdjson::dom::element_type::STRING: + result = LLSD(std::string(val.get_string().value_unsafe())); + break; + case simdjson::dom::element_type::BOOL: + result = LLSD(val.get_bool().value_unsafe()); + break; + 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()) + for (const simdjson::dom::key_value_pair& member : val.get_object().value_unsafe()) { - result[std::string_view(element.key())] = LlsdFromJson(element.value()); + result[member.key] = LlsdFromJson(member.value); } break; } @@ -84,48 +100,109 @@ LLSD LlsdFromJson(const boost::json::value& val) } //========================================================================= -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 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: @@ -134,6 +211,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); } diff --git a/indra/llcommon/llsdjson.h b/indra/llcommon/llsdjson.h index 415bbf48212..bee9e81b607 100644 --- a/indra/llcommon/llsdjson.h +++ b/indra/llcommon/llsdjson.h @@ -1,5 +1,5 @@ /** - * @file llsdjson.cpp + * @file llsdjson.h * @brief LLSD flexible data system * * $LicenseInfo:firstyear=2015&license=viewerlgpl$ @@ -27,17 +27,16 @@ #ifndef LL_LLSDJSON_H #define LL_LLSDJSON_H -#include #include -#include +#include #include "stdtypes.h" #include "llsd.h" -#include +#include -/// 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 @@ -53,10 +52,33 @@ /// /// 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 @@ -64,7 +86,7 @@ LLSD LlsdFromJson(const boost::json::value &val); /// TypeUndefined | null /// TypeBoolean | boolean /// TypeInteger | integer -/// TypeReal | real/numeric +/// TypeReal | real/numeric (non-finite values become null) /// TypeString | string /// TypeURI | string /// TypeDate | string @@ -72,6 +94,6 @@ LLSD LlsdFromJson(const boost::json::value &val); /// TypeMap | object /// TypeArray | array /// TypeBinary | unsupported -boost::json::value LlsdToJson(const LLSD &val); +std::string LlsdToJson(const LLSD& val); #endif // LL_LLSDJSON_H diff --git a/indra/llcommon/tests/llsdjson_test.cpp b/indra/llcommon/tests/llsdjson_test.cpp new file mode 100644 index 00000000000..77782b1b7d5 --- /dev/null +++ b/indra/llcommon/tests/llsdjson_test.cpp @@ -0,0 +1,300 @@ +/** + * @file llsdjson_test.cpp + * @brief LLSD <-> JSON conversion test cases. + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2026, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "lltut.h" + +#include "llsdjson.h" +#include "llsdutil.h" +#include "lldate.h" +#include "lluri.h" +#include "lluuid.h" + +#include +#include + +namespace tut +{ + struct llsdjson_data + { + // parse json text expecting success + LLSD parse(const std::string& json) + { + LLSD result; + std::string errmsg; + bool ok = LlsdFromJsonString(json, result, &errmsg); + ensure("parse failed for " + json + ": " + errmsg, ok); + return result; + } + }; + typedef test_group llsdjson_test; + typedef llsdjson_test::object llsdjson_object; + tut::llsdjson_test tllsdjson("LlsdJson"); + + // scalar parsing: types and values + template<> template<> + void llsdjson_object::test<1>() + { + LLSD val = parse("null"); + ensure_equals("null type", val.type(), LLSD::TypeUndefined); + + val = parse("true"); + ensure_equals("bool type", val.type(), LLSD::TypeBoolean); + ensure("bool value", val.asBoolean()); + + val = parse("false"); + ensure("false value", !val.asBoolean()); + + val = parse("42"); + ensure_equals("int type", val.type(), LLSD::TypeInteger); + ensure_equals("int value", val.asInteger(), 42); + + val = parse("-7"); + ensure_equals("negative int", val.asInteger(), -7); + + val = parse("3.25"); + ensure_equals("real type", val.type(), LLSD::TypeReal); + ensure_equals("real value", val.asReal(), 3.25); + + val = parse("\"hello world\""); + ensure_equals("string type", val.type(), LLSD::TypeString); + ensure_equals("string value", val.asString(), "hello world"); + } + + // integer vs real distinction is preserved by the parse + template<> template<> + void llsdjson_object::test<2>() + { + ensure_equals("1 is integer", parse("1").type(), LLSD::TypeInteger); + ensure_equals("1.0 is real", parse("1.0").type(), LLSD::TypeReal); + ensure_equals("1e0 is real", parse("1e0").type(), LLSD::TypeReal); + ensure_equals("0 is integer", parse("0").type(), LLSD::TypeInteger); + } + + // arrays: order, nesting, empties + template<> template<> + void llsdjson_object::test<3>() + { + LLSD val = parse("[1, \"two\", 3.0, null, [true]]"); + ensure_equals("array type", val.type(), LLSD::TypeArray); + ensure_equals("array size", val.size(), 5); + ensure_equals("elt 0", val[0].asInteger(), 1); + ensure_equals("elt 1", val[1].asString(), "two"); + ensure_equals("elt 2", val[2].asReal(), 3.0); + ensure_equals("elt 3 undefined", val[3].type(), LLSD::TypeUndefined); + ensure_equals("nested array", val[4][0].asBoolean(), true); + + val = parse("[]"); + ensure_equals("empty array type", val.type(), LLSD::TypeArray); + ensure_equals("empty array size", val.size(), 0); + } + + // objects: keys, nesting, empties + template<> template<> + void llsdjson_object::test<4>() + { + LLSD val = parse("{\"a\": 1, \"b\": {\"c\": [2, 3]}}"); + ensure_equals("map type", val.type(), LLSD::TypeMap); + ensure_equals("map size", val.size(), 2); + ensure_equals("member a", val["a"].asInteger(), 1); + ensure_equals("nested member", val["b"]["c"][1].asInteger(), 3); + + val = parse("{}"); + ensure_equals("empty map type", val.type(), LLSD::TypeMap); + ensure_equals("empty map size", val.size(), 0); + } + + // serialization golden strings + template<> template<> + void llsdjson_object::test<5>() + { + ensure_equals("undefined", LlsdToJson(LLSD()), "null"); + ensure_equals("true", LlsdToJson(LLSD(true)), "true"); + ensure_equals("false", LlsdToJson(LLSD(false)), "false"); + ensure_equals("integer", LlsdToJson(LLSD(17)), "17"); + ensure_equals("negative", LlsdToJson(LLSD(-17)), "-17"); + ensure_equals("real", LlsdToJson(LLSD(2.5)), "2.5"); + ensure_equals("string", LlsdToJson(LLSD("simple")), "\"simple\""); + ensure_equals("empty string", LlsdToJson(LLSD("")), "\"\""); + ensure_equals("empty map", LlsdToJson(LLSD::emptyMap()), "{}"); + ensure_equals("empty array", LlsdToJson(LLSD::emptyArray()), "[]"); + + LLSD map; + map["b"] = 2; + map["a"] = 1; + // LLSD maps iterate in sorted key order + ensure_equals("map", LlsdToJson(map), "{\"a\":1,\"b\":2}"); + + LLSD array; + array.append(1); + array.append("x"); + ensure_equals("array", LlsdToJson(array), "[1,\"x\"]"); + } + + // string escaping in serialization + template<> template<> + void llsdjson_object::test<6>() + { + ensure_equals("quote", LlsdToJson(LLSD("a\"b")), "\"a\\\"b\""); + ensure_equals("backslash", LlsdToJson(LLSD("a\\b")), "\"a\\\\b\""); + ensure_equals("newline", LlsdToJson(LLSD("a\nb")), "\"a\\nb\""); + ensure_equals("tab", LlsdToJson(LLSD("a\tb")), "\"a\\tb\""); + + // control character must be escaped one way or another; round-trip it + LLSD ctrl; + ensure("control char parses", + LlsdFromJsonString(LlsdToJson(LLSD(std::string("a\x01z"))), ctrl)); + ensure_equals("control char round-trip", ctrl.asString(), std::string("a\x01z")); + + // UTF-8 passes through unescaped or escaped, but must round-trip + std::string utf8("\xE3\x81\x93\xE3\x82\x93 caf\xC3\xA9"); + LLSD uni; + ensure("utf8 parses", LlsdFromJsonString(LlsdToJson(LLSD(utf8)), uni)); + ensure_equals("utf8 round-trip", uni.asString(), utf8); + } + + // special scalar types serialize as strings + template<> template<> + void llsdjson_object::test<7>() + { + LLUUID id("c96f9b8e-f5ad-4b72-b6f7-a2b30d1444d2"); + ensure_equals("uuid", LlsdToJson(LLSD(id)), + "\"c96f9b8e-f5ad-4b72-b6f7-a2b30d1444d2\""); + + LLDate date("2026-06-11T12:34:56Z"); + ensure_equals("date", LlsdToJson(LLSD(date)), "\"2026-06-11T12:34:56Z\""); + + LLURI uri("http://example.com/path"); + ensure_equals("uri", LlsdToJson(LLSD(uri)), "\"http://example.com/path\""); + } + + // non-finite reals degrade to null (JSON has no NaN/Infinity) + template<> template<> + void llsdjson_object::test<8>() + { + ensure_equals("nan", LlsdToJson(LLSD(std::numeric_limits::quiet_NaN())), "null"); + ensure_equals("inf", LlsdToJson(LLSD(std::numeric_limits::infinity())), "null"); + ensure_equals("-inf", LlsdToJson(LLSD(-std::numeric_limits::infinity())), "null"); + + // and stay valid inside containers + LLSD map; + map["bad"] = std::numeric_limits::quiet_NaN(); + ensure_equals("nan in map", LlsdToJson(map), "{\"bad\":null}"); + } + + // structured round trip preserves everything + template<> template<> + void llsdjson_object::test<9>() + { + LLSD src; + src["int"] = 123; + src["real"] = 0.1; + src["string"] = "round trip"; + src["flag"] = true; + src["empty"] = LLSD(); + src["list"][0] = 1; + src["list"][1] = "two"; + src["list"][2]["deep"] = 3.5; + + LLSD dst; + std::string json = LlsdToJson(src); + ensure("round-trip parses: " + json, LlsdFromJsonString(json, dst)); + ensure("round-trip equality: " + json, llsd_equals(src, dst)); + } + + // full-precision reals survive the round trip + template<> template<> + void llsdjson_object::test<10>() + { + F64 vals[] = { 0.1, 1.0/3.0, 2.718281828459045, 1e-300, 1.7976931348623157e308 }; + for (F64 v : vals) + { + LLSD parsed; + ensure("real parses", LlsdFromJsonString(LlsdToJson(LLSD(v)), parsed)); + ensure_equals("real precision", parsed.asReal(), v); + } + } + + // parse failures report errors and clear the output + template<> template<> + void llsdjson_object::test<11>() + { + const char* bad[] = + { + "", + " ", + "{", + "[1, 2", + "{\"a\": }", + "{} trailing", + "'single quotes'", + "{\"a\": 1,}", + "\"unterminated", + }; + for (const char* json : bad) + { + LLSD out = LLSD::emptyMap(); + std::string errmsg; + bool ok = LlsdFromJsonString(json, out, &errmsg); + ensure(std::string("rejects: ") + json, !ok); + ensure(std::string("errmsg set for: ") + json, !errmsg.empty()); + ensure_equals(std::string("output cleared for: ") + json, + out.type(), LLSD::TypeUndefined); + } + + // errmsg pointer is optional + LLSD out; + ensure("no errmsg crash", !LlsdFromJsonString("{", out)); + } + + // unicode object keys + template<> template<> + void llsdjson_object::test<12>() + { + LLSD src; + src["caf\xC3\xA9"] = 1; + LLSD dst; + ensure("unicode key parses", LlsdFromJsonString(LlsdToJson(src), dst)); + ensure_equals("unicode key value", dst["caf\xC3\xA9"].asInteger(), 1); + } + + // padded_string overload parses in place + template<> template<> + void llsdjson_object::test<13>() + { + LLSD out; + ensure("padded parse", + LlsdFromJsonString(simdjson::padded_string(std::string("{\"a\": [1, 2.5, \"three\"]}")), out)); + ensure_equals("padded int", out["a"][0].asInteger(), 1); + ensure_equals("padded real", out["a"][1].asReal(), 2.5); + ensure_equals("padded string", out["a"][2].asString(), "three"); + + std::string errmsg; + ensure("padded parse fails", + !LlsdFromJsonString(simdjson::padded_string(std::string("{")), out, &errmsg)); + ensure("padded errmsg set", !errmsg.empty()); + } +} diff --git a/indra/llcorehttp/lljsonrpcws.cpp b/indra/llcorehttp/lljsonrpcws.cpp index 93e38a8397c..461b69bccf0 100644 --- a/indra/llcorehttp/lljsonrpcws.cpp +++ b/indra/llcorehttp/lljsonrpcws.cpp @@ -31,8 +31,6 @@ #include "llsdjson.h" #include "lldate.h" -#include - //======================================================================== // LLJSONRPCConnection Implementation //======================================================================== @@ -67,20 +65,16 @@ void LLJSONRPCConnection::onMessage(const std::string& message) try { - // Parse JSON message - boost::system::error_code ec; - boost::json::value json_value = boost::json::parse(message, ec); - - if (ec.failed()) + // Parse JSON message and convert to LLSD + LLSD message_obj; + std::string errmsg; + if (!LlsdFromJsonString(message, message_obj, &errmsg)) { - LL_WARNS("JSONRPC") << "Failed to parse JSON: " << ec.message() << LL_ENDL; - sendError(LLSD(), ParseError(ec.message())); + LL_WARNS("JSONRPC") << "Failed to parse JSON: " << errmsg << LL_ENDL; + sendError(LLSD(), ParseError(errmsg)); return; } - // Convert to LLSD - LLSD message_obj = LlsdFromJson(json_value); - // Handle batch vs single message if (message_obj.isArray()) { @@ -571,7 +565,7 @@ void LLJSONRPCServer::broadcastNotification(const std::string& method, const LLS } // Use the base class broadcast functionality - broadcastMessage(boost::json::serialize(LlsdToJson(notification))); + broadcastMessage(LlsdToJson(notification)); mTotalNotificationsSent += getConnectionCount(); LL_DEBUGS("JSONRPC") << "Broadcast notification: " << method @@ -602,7 +596,7 @@ void LLJSONRPCServer::broadcastCall(const std::string& method, const LLSD& param } // Use the base class broadcast functionality - broadcastMessage(boost::json::serialize(LlsdToJson(request))); + broadcastMessage(LlsdToJson(request)); LL_DEBUGS("JSONRPC") << "Broadcast call: " << method << " to " << getConnectionCount() << " clients" << LL_ENDL; diff --git a/indra/llcorehttp/llwebsocketmgr.cpp b/indra/llcorehttp/llwebsocketmgr.cpp index 304f2ef7293..55065cdb724 100644 --- a/indra/llcorehttp/llwebsocketmgr.cpp +++ b/indra/llcorehttp/llwebsocketmgr.cpp @@ -630,12 +630,6 @@ bool LLWebsocketMgr::WSConnection::sendMessage(const std::string& message) const return mOwningServer.lock()->sendMessageTo(mConnectionHandle, message); } -bool LLWebsocketMgr::WSConnection::sendMessage(const boost::json::value& json) const -{ - std::string message = boost::json::serialize(json); - return sendMessage(message); -} - bool LLWebsocketMgr::WSConnection::sendMessage(const LLSD& data) const { return sendMessage(LlsdToJson(data)); diff --git a/indra/llcorehttp/llwebsocketmgr.h b/indra/llcorehttp/llwebsocketmgr.h index 4165b3cecc4..53a356fb88d 100644 --- a/indra/llcorehttp/llwebsocketmgr.h +++ b/indra/llcorehttp/llwebsocketmgr.h @@ -39,8 +39,6 @@ #include #include -#include - #include struct Server_impl; @@ -123,7 +121,6 @@ class LLWebsocketMgr: public LLSingleton * asynchronously and may not be sent immediately. */ bool sendMessage(const std::string& message) const; - bool sendMessage(const boost::json::value& json) const; bool sendMessage(const LLSD& data) const; /** diff --git a/indra/llmessage/llcorehttputil.cpp b/indra/llmessage/llcorehttputil.cpp index 7e619d45e3e..019f2e15898 100644 --- a/indra/llmessage/llcorehttputil.cpp +++ b/indra/llmessage/llcorehttputil.cpp @@ -35,7 +35,6 @@ #include "llsd.h" #include "llsdjson.h" #include "llsdserialize.h" -#include "boost/json.hpp" // Boost.Json #include "llfilesystem.h" #include "workqueue.h" @@ -629,19 +628,17 @@ LLSD HttpCoroJSONHandler::handleSuccess(LLCore::HttpResponse * response, LLCore: return result; } - LLCore::BufferArrayStream bas(body); + // read into a padded buffer so the parse runs in place with no extra copy + simdjson::padded_string bodyData(body->size()); + body->read(0, bodyData.data(), bodyData.size()); - boost::system::error_code ec; - boost::json::value jsonRoot = boost::json::parse(bas, ec); - if(ec.failed()) + std::string errmsg; + if (!LlsdFromJsonString(bodyData, result, &errmsg)) { // deserialization failed. Record the reason and pass back an empty map for markup. - status = LLCore::HttpStatus(499, std::string(ec.what())); - return result; + status = LLCore::HttpStatus(499, errmsg); + result = LLSD::emptyMap(); } - // Convert the JSON structure to LLSD - result = LlsdFromJson(jsonRoot); - return result; } @@ -654,18 +651,18 @@ LLSD HttpCoroJSONHandler::parseBody(LLCore::HttpResponse *response, bool &succes return LLSD(); } - LLCore::BufferArrayStream bas(body); + // read into a padded buffer so the parse runs in place with no extra copy + simdjson::padded_string bodyData(body->size()); + body->read(0, bodyData.data(), bodyData.size()); - boost::system::error_code ec; - boost::json::value jsonRoot = boost::json::parse(bas, ec); - if (ec.failed()) + LLSD result; + if (!LlsdFromJsonString(bodyData, result)) { success = false; return LLSD(); } - // Convert the JSON structure to LLSD - return LlsdFromJson(jsonRoot); + return result; } //======================================================================== @@ -841,8 +838,7 @@ LLSD HttpCoroutineAdapter::postJsonAndSuspend(LLCore::HttpRequest::ptr_t request { LLCore::BufferArrayStream outs(rawbody.get()); - auto root = LlsdToJson(body); - std::string value = boost::json::serialize(root); + std::string value = LlsdToJson(body); LL_WARNS("Http::post") << "JSON Generates: \"" << value << "\"" << LL_ENDL; @@ -900,8 +896,7 @@ LLSD HttpCoroutineAdapter::putJsonAndSuspend(LLCore::HttpRequest::ptr_t request, { LLCore::BufferArrayStream outs(rawbody.get()); - auto root = LlsdToJson(body); - std::string value = boost::json::serialize(root); + std::string value = LlsdToJson(body); LL_WARNS("Http::put") << "JSON Generates: \"" << value << "\"" << LL_ENDL; outs << value; diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index ddeaaed0f56..0d6b15cfd3c 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -64,7 +64,7 @@ U64 LLGLSLShader::sTotalTimeElapsed = 0; U32 LLGLSLShader::sTotalTrianglesDrawn = 0; U64 LLGLSLShader::sTotalSamplesDrawn = 0; U32 LLGLSLShader::sTotalBinds = 0; -boost::json::value LLGLSLShader::sDefaultStats; +LLSD LLGLSLShader::sDefaultStats; //UI shader -- declared here so llui_libtest will link properly LLGLSLShader gUIProgram; @@ -119,18 +119,17 @@ struct LLGLSLShaderCompareTimeElapsed }; //static -void LLGLSLShader::finishProfile(boost::json::value& statsv) +void LLGLSLShader::finishProfile(LLSD& stats) { sProfileEnabled = false; - if (! statsv.is_null()) + if (stats.isDefined()) { std::vector sorted(sInstances.begin(), sInstances.end()); std::sort(sorted.begin(), sorted.end(), LLGLSLShaderCompareTimeElapsed()); - auto& stats = statsv.as_object(); - auto shadersit = stats.emplace("shaders", boost::json::array_kind).first; - auto& shaders = shadersit->value().as_array(); + LLSD& shaders = stats["shaders"]; + shaders = LLSD::emptyArray(); bool unbound = false; for (auto ptr : sorted) { @@ -140,8 +139,8 @@ void LLGLSLShader::finishProfile(boost::json::value& statsv) } else { - auto& shaderit = shaders.emplace_back(boost::json::object_kind); - ptr->dumpStats(shaderit.as_object()); + LLSD& shaderit = shaders.append(LLSD::emptyMap()); + ptr->dumpStats(shaderit); } } @@ -152,15 +151,16 @@ void LLGLSLShader::finishProfile(boost::json::value& statsv) LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / mega) << LL_ENDL; LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / mega) << LL_ENDL; LL_INFOS() << "-----------------------------------" << LL_ENDL; - auto totalsit = stats.emplace("totals", boost::json::object_kind).first; - auto& totals = totalsit->value().as_object(); - totals.emplace("time", totalTimeMs / 1000.0); - totals.emplace("binds", sTotalBinds); - totals.emplace("samples", sTotalSamplesDrawn); - totals.emplace("triangles", sTotalTrianglesDrawn); - - auto unusedit = stats.emplace("unused", boost::json::array_kind).first; - auto& unused = unusedit->value().as_array(); + LLSD& totals = stats["totals"]; + totals = LLSD::emptyMap(); + totals["time"] = totalTimeMs / 1000.0; + totals["binds"] = LLSD::Integer(sTotalBinds); + // sample counters are 64-bit; store as Real to avoid S32 overflow + totals["samples"] = F64(sTotalSamplesDrawn); + totals["triangles"] = LLSD::Integer(sTotalTrianglesDrawn); + + LLSD& unused = stats["unused"]; + unused = LLSD::emptyArray(); if (unbound) { LL_INFOS() << "The following shaders were unused: " << LL_ENDL; @@ -169,7 +169,7 @@ void LLGLSLShader::finishProfile(boost::json::value& statsv) if (ptr->mBinds == 0) { LL_INFOS() << ptr->mName << LL_ENDL; - unused.emplace_back(ptr->mName); + unused.append(ptr->mName); } } } @@ -184,17 +184,17 @@ void LLGLSLShader::clearStats() mBinds = 0; } -void LLGLSLShader::dumpStats(boost::json::object& stats) +void LLGLSLShader::dumpStats(LLSD& stats) { - stats.emplace("name", mName); - auto filesit = stats.emplace("files", boost::json::array_kind).first; - auto& files = filesit->value().as_array(); + stats["name"] = mName; + LLSD& files = stats["files"]; + files = LLSD::emptyArray(); LL_INFOS() << "=============================================" << LL_ENDL; LL_INFOS() << mName << LL_ENDL; for (U32 i = 0; i < mShaderFiles.size(); ++i) { LL_INFOS() << mShaderFiles[i].first << LL_ENDL; - files.emplace_back(mShaderFiles[i].first); + files.append(mShaderFiles[i].first); } LL_INFOS() << "=============================================" << LL_ENDL; @@ -217,10 +217,11 @@ void LLGLSLShader::dumpStats(boost::json::object& stats) LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; - stats.emplace("time", seconds); - stats.emplace("binds", mBinds); - stats.emplace("samples", mSamplesDrawn); - stats.emplace("triangles", mTrianglesDrawn); + stats["time"] = seconds; + stats["binds"] = LLSD::Integer(mBinds); + // sample counters are 64-bit; store as Real to avoid S32 overflow + stats["samples"] = F64(mSamplesDrawn); + stats["triangles"] = LLSD::Integer(mTrianglesDrawn); } //static diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index df3cbd3b74f..704ecf90a13 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -29,8 +29,8 @@ #include "llgl.h" #include "llrender.h" +#include "llsd.h" #include "llstaticstringtable.h" -#include #include class LLShaderFeatures @@ -175,14 +175,14 @@ class LLGLSLShader static U32 sMaxGLTFNodes; static void initProfile(); - static void finishProfile(boost::json::value& stats=sDefaultStats); + static void finishProfile(LLSD& stats=sDefaultStats); static void startProfile(); static void stopProfile(); void unload(); void clearStats(); - void dumpStats(boost::json::object& stats); + void dumpStats(LLSD& stats); // place query objects for profiling if profiling is enabled // if for_runtime is true, will place timer query only whether or not profiling is enabled @@ -373,9 +373,9 @@ class LLGLSLShader void unloadInternal(); // This must be static because finishProfile() is called at least once // within a __try block. If we default its stats parameter to a temporary - // json::value, that temporary must be destroyed when the stack is - // unwound, which __try forbids. - static boost::json::value sDefaultStats; + // LLSD, that temporary must be destroyed when the stack is unwound, + // which __try forbids. + static LLSD sDefaultStats; }; //UI shader (declared here so llui_libtest will link properly) diff --git a/indra/newview/gltf/README.md b/indra/newview/gltf/README.md index a2d43be1d61..25e79dc2c31 100644 --- a/indra/newview/gltf/README.md +++ b/indra/newview/gltf/README.md @@ -17,19 +17,24 @@ The implementation serves both the client and the server. - The implementation MUST be capable of round-trip serialization with no data loss beyond F64 to F32 conversions. - The implementation MUST use the same indexing scheme as the GLTF specification. Do not store pointers where the - GLTF specification stores indices, store indices. -- Limit dependencies on llcommon as much as possible. Prefer std::, boost::, and (soon) glm:: over LL facsimiles. +- Limit dependencies on llcommon as much as possible. Prefer std::, simdjson, and glm:: over LL facsimiles. - Usage of LLSD is forbidden in the LL::GLTF namespace. - Use "using namespace" liberally in .cpp files, but never in .h files. - "using Foo = Bar" is permissible in .h files within the LL::GLTF namespace. ## Loading, Copying, and Serialization +JSON parsing uses simdjson ("Value" is an alias of simdjson::dom::element, a +read-only handle into a parsed document). Serialization streams JSON text +through JsonWriter (common.h), a thin comma/colon-managing wrapper over +simdjson's string_builder -- members are emitted in call order, there is no +mutable document tree. + Each class should provide two functions (Primitive shown for example): ``` -// Serialize to the provided json object. -// "obj" should be "this" in json form on return +// Append "this" in json form to the provided writer // Do not serialize default values -void serialize(boost::json::object& obj) const; +void serialize(JsonWriter& obj) const; // Initialize from a provided json value const Primitive& operator=(const Value& src); @@ -38,10 +43,10 @@ const Primitive& operator=(const Value& src); "serialize" implementations should use "write": ``` -void Primitive::serialize(boost::json::object& dst) const +void Primitive::serialize(JsonWriter& dst) const { write(mMaterial, "material", dst, -1); - write(mMode, "mode", dst, TINYGLTF_MODE_TRIANGLES); + write(mMode, "mode", dst, Mode::TRIANGLES); write(mIndices, "indices", dst, INVALID_INDEX); write(mAttributes, "attributes", dst); } @@ -84,17 +89,15 @@ example of how to add support for a new type (there are bad examples, so beware) template<> inline bool copy(const Value& src, vec3& dst) { - if (src.is_array()) + simdjson::dom::array arr; + if (src.get_array().get(arr) == simdjson::SUCCESS && arr.size() == 3) { - const boost::json::array& arr = src.as_array(); - if (arr.size() == 3) + vec3 t; + if (to_float(arr.at(0).value_unsafe(), t.x) && + to_float(arr.at(1).value_unsafe(), t.y) && + to_float(arr.at(2).value_unsafe(), t.z)) { - if (arr[0].is_double() && - arr[1].is_double() && - arr[2].is_double()) - { - dst = vec3(arr[0].get_double(), arr[1].get_double(), arr[2].get_double()); - } + dst = t; return true; } } @@ -102,14 +105,13 @@ inline bool copy(const Value& src, vec3& dst) } template<> -inline bool write(const vec3& src, Value& dst) +inline bool write(const vec3& src, JsonWriter& dst) { - dst = boost::json::array(); - boost::json::array& arr = dst.as_array(); - arr.resize(3); - arr[0] = src.x; - arr[1] = src.y; - arr[2] = src.z; + dst.startArray(); + dst.value(src.x); + dst.value(src.y); + dst.value(src.z); + dst.endArray(); return true; } @@ -118,17 +120,14 @@ inline bool write(const vec3& src, Value& dst) "write" MUST return true if ANY data was written "copy" MUST return true if ANY data was copied -Speed is important, but so is safety. In writers, try to avoid redundant copies -(prefer resize over push_back, convert dst to an empty array and fill it, don't -make an array on the stack and copy it into dst). +Speed is important, but so is safety. In writers, stream values directly -- +don't build temporary containers and copy them. -boost::json WILL throw exceptions if you call as_foo() on a mismatched type but -WILL NOT throw exceptions on get_foo with a mismatched type. ALWAYS check is_foo -before calling as_foo or get_foo. DO NOT add exception handlers. If boost throws -an exception in serialization, the fix is to add type checks. If we see a large -number of crash reports from boost::json exceptions, each of those reports -indicates a place where we're missing "is_foo" checks. They are gold. Do not -bury them with an exception handler. +simdjson is used through its error-code interface: every accessor returns a +simdjson_result that must be checked with .get(out) == simdjson::SUCCESS before +the value is used. DO NOT add exception handlers and DO NOT use the throwing +value() accessors in serialization paths. If a type mismatch is possible, the +fix is to handle the error code. DO NOT rely on existing type conversion tools in the LL codebase -- LL data models conflict with the GLTF specification so we MUST provide conversions independent of @@ -141,7 +140,7 @@ our existing implementations. NEVER include buffer_util.h from a header. Loading from and saving to disk (import/export) is currently done using tinygltf, but this is not a long term -solution. Eventually the implementation should rely solely on boost::json for reading and writing .gltf +solution. Eventually the implementation should rely solely on simdjson/JsonWriter for reading and writing .gltf files and should handle .bin files natively. When serializing Images and Buffers to the server, clients MUST store a single UUID "uri" field and nothing else. diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 900c1da170c..95bbe9fc36d 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -31,7 +31,6 @@ #include "llfilesystem.h" using namespace LL::GLTF; -using namespace boost::json; namespace LL { @@ -225,7 +224,7 @@ bool Buffer::save(Asset& asset, const std::string& folder) return true; } -void Buffer::serialize(object& dst) const +void Buffer::serialize(JsonWriter& dst) const { write(mName, "name", dst); write(mUri, "uri", dst); @@ -247,7 +246,7 @@ const Buffer& Buffer::operator=(const Value& src) return *this; } -void BufferView::serialize(object& dst) const +void BufferView::serialize(JsonWriter& dst) const { write_always(mBuffer, "buffer", dst); write_always(mByteLength, "byteLength", dst); @@ -271,7 +270,7 @@ const BufferView& BufferView::operator=(const Value& src) return *this; } -void Accessor::serialize(object& dst) const +void Accessor::serialize(JsonWriter& dst) const { write(mName, "name", dst); write(mBufferView, "bufferView", dst, INVALID_INDEX); diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h index 85ea0f2967d..b45796be7c4 100644 --- a/indra/newview/gltf/accessor.h +++ b/indra/newview/gltf/accessor.h @@ -27,7 +27,6 @@ */ #include "llstrider.h" -#include "boost/json.hpp" #include "common.h" @@ -50,7 +49,7 @@ namespace LL bool prep(Asset& asset); - void serialize(boost::json::object& obj) const; + void serialize(JsonWriter& obj) const; const Buffer& operator=(const Value& value); bool save(Asset& asset, const std::string& folder); @@ -67,7 +66,7 @@ namespace LL std::string mName; - void serialize(boost::json::object& obj) const; + void serialize(JsonWriter& obj) const; const BufferView& operator=(const Value& value); }; @@ -105,7 +104,7 @@ namespace LL Type mType = Type::SCALAR; bool mNormalized = false; - void serialize(boost::json::object& obj) const; + void serialize(JsonWriter& obj) const; const Accessor& operator=(const Value& value); }; diff --git a/indra/newview/gltf/animation.cpp b/indra/newview/gltf/animation.cpp index 31549986af4..1716461448a 100644 --- a/indra/newview/gltf/animation.cpp +++ b/indra/newview/gltf/animation.cpp @@ -31,7 +31,6 @@ #include "../llskinningutil.h" using namespace LL::GLTF; -using namespace boost::json; bool Animation::prep(Asset& asset) { @@ -139,7 +138,7 @@ bool Animation::Sampler::prep(Asset& asset) } -void Animation::Sampler::serialize(object& obj) const +void Animation::Sampler::serialize(JsonWriter& obj) const { write(mInput, "input", obj, INVALID_INDEX); write(mOutput, "output", obj, INVALID_INDEX); @@ -171,7 +170,7 @@ bool Animation::Channel::Target::operator!=(const Channel::Target& rhs) const return !(*this == rhs); } -void Animation::Channel::Target::serialize(object& obj) const +void Animation::Channel::Target::serialize(JsonWriter& obj) const { write(mNode, "node", obj, INVALID_INDEX); write(mPath, "path", obj); @@ -187,7 +186,7 @@ const Animation::Channel::Target& Animation::Channel::Target::operator=(const Va return *this; } -void Animation::Channel::serialize(object& obj) const +void Animation::Channel::serialize(JsonWriter& obj) const { write(mSampler, "sampler", obj, INVALID_INDEX); write(mTarget, "target", obj); @@ -345,7 +344,7 @@ void Animation::ScaleChannel::apply(Asset& asset, Sampler& sampler, F32 time) } } -void Animation::serialize(object& obj) const +void Animation::serialize(JsonWriter& obj) const { write(mName, "name", obj); write(mSamplers, "samplers", obj); @@ -360,10 +359,9 @@ void Animation::serialize(object& obj) const const Animation& Animation::operator=(const Value& src) { - if (src.is_object()) + simdjson::dom::object obj; + if (src.get_object().get(obj) == simdjson::SUCCESS) { - const object& obj = src.as_object(); - copy(obj, "name", mName); copy(obj, "samplers", mSamplers); @@ -480,7 +478,7 @@ const Skin& Skin::operator=(const Value& src) return *this; } -void Skin::serialize(object& obj) const +void Skin::serialize(JsonWriter& obj) const { write(mInverseBindMatrices, "inverseBindMatrices", obj, INVALID_INDEX); write(mJoints, "joints", obj); diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h index ab8839470a3..5dd53741ad0 100644 --- a/indra/newview/gltf/animation.h +++ b/indra/newview/gltf/animation.h @@ -54,7 +54,7 @@ namespace LL bool prep(Asset& asset); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; const Sampler& operator=(const Value& value); // get the frame index and time for the specified time @@ -77,14 +77,14 @@ namespace LL bool operator==(const Target& other) const; bool operator!=(const Target& other) const; - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; const Target& operator=(const Value& value); }; S32 mSampler = INVALID_INDEX; Target mTarget; - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; const Channel& operator=(const Value& value); }; @@ -150,7 +150,7 @@ namespace LL std::vector mTranslationChannels; std::vector mScaleChannels; - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; const Animation& operator=(const Value& value); bool prep(Asset& asset); diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 99473e67c74..ed5f08fe160 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -39,7 +39,6 @@ #include using namespace LL::GLTF; -using namespace boost::json; namespace LL @@ -387,7 +386,7 @@ void Node::setScale(const vec3& s) mMatrixValid = false; } -void Node::serialize(object& dst) const +void Node::serialize(JsonWriter& dst) const { write(mName, "name", dst); write(mMatrix, "matrix", dst, glm::identity()); @@ -418,7 +417,7 @@ const Node& Node::operator=(const Value& src) return *this; } -void Image::serialize(object& dst) const +void Image::serialize(JsonWriter& dst) const { write(mUri, "uri", dst); write(mMimeType, "mimeType", dst); @@ -709,7 +708,13 @@ bool Asset::load(std::string_view filename, bool loadIntoVRAM) if (ext == "gltf") { - Value val = parse(str); + simdjson::dom::parser parser; + Value val; + if (parser.parse(str).get(val) != simdjson::SUCCESS) + { + LL_WARNS("GLTF") << "Failed to parse: " << mFilename << LL_ENDL; + return false; + } *this = val; return prep(); } @@ -796,7 +801,13 @@ bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM) return false; } - Value val = parse(std::string_view((const char*)ptr, chunkLength)); + simdjson::dom::parser parser; + Value val; + if (parser.parse((const char*)ptr, chunkLength).get(val) != simdjson::SUCCESS) + { + LL_WARNS("GLTF") << "Failed to parse GLB JSON chunk" << LL_ENDL; + return false; + } *this = val; if (mBuffers.size() > 0 && mBuffers[0].mUri.empty()) @@ -839,21 +850,23 @@ bool Asset::loadBinary(const std::string& data, bool loadIntoVRAM) const Asset& Asset::operator=(const Value& src) { - if (src.is_object()) + simdjson::dom::object obj; + if (src.get_object().get(obj) == simdjson::SUCCESS) { - const object& obj = src.as_object(); - - const auto it = obj.find("asset"); - - if (it != obj.end()) + Value asset; + if (obj["asset"].get(asset) == simdjson::SUCCESS) { - const Value& asset = it->value(); - copy(asset, "version", mVersion); copy(asset, "minVersion", mMinVersion); copy(asset, "generator", mGenerator); copy(asset, "copyright", mCopyright); - copy(asset, "extras", mExtras); + + // preserve the arbitrary extras subtree as minified JSON text + Value extras; + if (asset["extras"].get(extras) == simdjson::SUCCESS) + { + mExtras = simdjson::to_string(extras); + } } copy(obj, "scene", mScene); @@ -876,16 +889,17 @@ const Asset& Asset::operator=(const Value& src) return *this; } -void Asset::serialize(object& dst) const +void Asset::serialize(JsonWriter& dst) const { static const std::string sGenerator = "Linden Lab GLTF Prototype v0.1"; - dst["asset"] = object{}; - object& asset = dst["asset"].get_object(); + dst.key("asset"); + dst.startObject(); + write(mVersion, "version", dst); + write(mMinVersion, "minVersion", dst, std::string()); + write(sGenerator, "generator", dst); + dst.endObject(); - write(mVersion, "version", asset); - write(mMinVersion, "minVersion", asset, std::string()); - write(sGenerator, "generator", asset); write(mScene, "scene", dst, INVALID_INDEX); write(mScenes, "scenes", dst); write(mNodes, "nodes", dst); @@ -929,9 +943,11 @@ bool Asset::save(const std::string& filename) } // save .gltf - object obj; - serialize(obj); - std::string buffer = boost::json::serialize(obj, {}); + JsonWriter writer; + writer.startObject(); + serialize(writer); + writer.endObject(); + std::string buffer = writer.str(); llofstream file(filename, std::ios::binary); file.write(buffer.c_str(), buffer.size()); @@ -1195,7 +1211,7 @@ bool Image::save(Asset& asset, const std::string& folder) return true; } -void TextureInfo::serialize(object& dst) const +void TextureInfo::serialize(JsonWriter& dst) const { write(mIndex, "index", dst, INVALID_INDEX); write(mTexCoord, "texCoord", dst, 0); @@ -1242,7 +1258,7 @@ bool TextureInfo::operator!=(const TextureInfo& rhs) const return !(*this == rhs); } -void OcclusionTextureInfo::serialize(object& dst) const +void OcclusionTextureInfo::serialize(JsonWriter& dst) const { TextureInfo::serialize(dst); write(mStrength, "strength", dst, 1.f); @@ -1260,7 +1276,7 @@ const OcclusionTextureInfo& OcclusionTextureInfo::operator=(const Value& src) return *this; } -void NormalTextureInfo::serialize(object& dst) const +void NormalTextureInfo::serialize(JsonWriter& dst) const { TextureInfo::serialize(dst); write(mScale, "scale", dst, 1.f); @@ -1293,7 +1309,7 @@ const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=( return *this; } -void Material::PbrMetallicRoughness::serialize(object& dst) const +void Material::PbrMetallicRoughness::serialize(JsonWriter& dst) const { write(mBaseColorFactor, "baseColorFactor", dst, vec4(1.f, 1.f, 1.f, 1.f)); write(mBaseColorTexture, "baseColorTexture", dst); @@ -1322,7 +1338,7 @@ const Material::Unlit& Material::Unlit::operator=(const Value& src) return *this; } -void Material::Unlit::serialize(object& dst) const +void Material::Unlit::serialize(JsonWriter& dst) const { // no members and object has already been created, nothing to do } @@ -1347,7 +1363,7 @@ const TextureTransform& TextureTransform::operator=(const Value& src) return *this; } -void TextureTransform::serialize(object& dst) const +void TextureTransform::serialize(JsonWriter& dst) const { write(mOffset, "offset", dst, vec2(0.f, 0.f)); write(mRotation, "rotation", dst, 0.f); @@ -1356,7 +1372,7 @@ void TextureTransform::serialize(object& dst) const } -void Material::serialize(object& dst) const +void Material::serialize(JsonWriter& dst) const { write(mName, "name", dst); write(mEmissiveFactor, "emissiveFactor", dst, vec3(0.f, 0.f, 0.f)); @@ -1390,7 +1406,7 @@ const Material& Material::operator=(const Value& src) } -void Mesh::serialize(object& dst) const +void Mesh::serialize(JsonWriter& dst) const { write(mPrimitives, "primitives", dst); write(mWeights, "weights", dst); @@ -1422,7 +1438,7 @@ bool Mesh::prep(Asset& asset) return true; } -void Scene::serialize(object& dst) const +void Scene::serialize(JsonWriter& dst) const { write(mNodes, "nodes", dst); write(mName, "name", dst); @@ -1436,7 +1452,7 @@ const Scene& Scene::operator=(const Value& src) return *this; } -void Texture::serialize(object& dst) const +void Texture::serialize(JsonWriter& dst) const { write(mSampler, "sampler", dst, INVALID_INDEX); write(mSource, "source", dst, INVALID_INDEX); @@ -1455,7 +1471,7 @@ const Texture& Texture::operator=(const Value& src) return *this; } -void Sampler::serialize(object& dst) const +void Sampler::serialize(JsonWriter& dst) const { write(mMagFilter, "magFilter", dst, LINEAR); write(mMinFilter, "minFilter", dst, LINEAR_MIPMAP_LINEAR); diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 2802664ed37..dc80e6c8527 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -31,7 +31,6 @@ #include "accessor.h" #include "primitive.h" #include "animation.h" -#include "boost/json.hpp" #include "common.h" #include "../llviewertexture.h" #include "llglslshader.h" @@ -71,7 +70,7 @@ namespace LL void getPacked(vec4* dst) const; const TextureTransform& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; class TextureInfo @@ -90,7 +89,7 @@ namespace LL S32 getTexCoord() const; const TextureInfo& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; class NormalTextureInfo : public TextureInfo @@ -99,7 +98,7 @@ namespace LL F32 mScale = 1.0f; const NormalTextureInfo& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; class OcclusionTextureInfo : public TextureInfo @@ -108,7 +107,7 @@ namespace LL F32 mStrength = 1.0f; const OcclusionTextureInfo& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; class Material @@ -119,7 +118,7 @@ namespace LL { public: const Unlit& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; enum class AlphaMode @@ -142,7 +141,7 @@ namespace LL bool operator==(const PbrMetallicRoughness& rhs) const; bool operator!=(const PbrMetallicRoughness& rhs) const; const PbrMetallicRoughness& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; @@ -161,7 +160,7 @@ namespace LL bool isMultiUV() const; const Material& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; class Mesh @@ -172,7 +171,7 @@ namespace LL std::string mName; const Mesh& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; bool prep(Asset& asset); }; @@ -205,7 +204,7 @@ namespace LL std::string mName; const Node& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; // update mAssetMatrix and mAssetMatrixInv void updateTransforms(Asset& asset, const mat4& parentMatrix); @@ -246,7 +245,7 @@ namespace LL void uploadMatrixPalette(Asset& asset); const Skin& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; class Scene @@ -256,7 +255,7 @@ namespace LL std::string mName; const Scene& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; void updateTransforms(Asset& asset); void updateRenderTransforms(Asset& asset, const mat4& modelview); @@ -270,7 +269,7 @@ namespace LL std::string mName; const Texture& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; class Sampler @@ -283,7 +282,7 @@ namespace LL std::string mName; const Sampler& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; }; // Image is for images that we want to load for the given asset. This acts as an interface into the viewer's texture pipe. @@ -307,7 +306,7 @@ namespace LL LLPointer mTexture; const Image& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; // save image to disk // may remove image data from bufferviews and convert to @@ -376,7 +375,8 @@ namespace LL std::string mCopyright; S32 mScene = INVALID_INDEX; - Value mExtras; + // "asset.extras" subtree preserved as minified JSON text + std::string mExtras; U32 mPendingBuffers = 0; @@ -445,7 +445,7 @@ namespace LL bool loadBinary(const std::string& data, bool loadIntoVRAM); const Asset& operator=(const Value& src); - void serialize(boost::json::object& dst) const; + void serialize(JsonWriter& dst) const; // save the asset to the given .gltf file // saves images and bins alongside the gltf file diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index 53dee98cd1f..b7651467118 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -43,7 +43,7 @@ namespace LL namespace GLTF { - using string_view = boost::json::string_view; + using string_view = std::string_view; // copy one Scalar from src to dst template @@ -475,9 +475,22 @@ namespace LL //========================================================================================================= - // boost::json copying utilities + // JSON copying utilities. Reads come from a parsed simdjson document + // (Value), writes stream JSON text through JsonWriter. // ======================================================================================================== + // read any JSON number into a float, mirroring numeric coercion + inline bool to_float(const Value& src, F32& dst) + { + double d; + if (src.get_double().get(d) == simdjson::SUCCESS) + { + dst = (F32)d; + return true; + } + return false; + } + //====================== unspecialized base template, single value =========================== // to/from Value @@ -489,22 +502,23 @@ namespace LL } template - inline bool write(const T& src, Value& dst) + inline bool write(const T& src, JsonWriter& dst) { - dst = boost::json::object(); - src.serialize(dst.as_object()); + dst.startObject(); + src.serialize(dst); + dst.endObject(); return true; } template inline bool copy(const Value& src, std::unordered_map& dst) { - if (src.is_object()) + simdjson::dom::object obj; + if (src.get_object().get(obj) == simdjson::SUCCESS) { - const boost::json::object& obj = src.as_object(); - for (const auto& [key, value] : obj) + for (const auto& member : obj) { - copy(value, dst[key]); + copy(member.value, dst[std::string(member.key)]); } return true; } @@ -512,22 +526,15 @@ namespace LL } template - inline bool write(const std::unordered_map& src, Value& dst) + inline bool write(const std::unordered_map& src, JsonWriter& dst) { - boost::json::object obj; + dst.startObject(); for (const auto& [key, value] : src) { - Value v; - if (write(value, v)) - { - obj[key] = v; - } - else - { - return false; - } + dst.key(key); + write(value, dst); } - dst = obj; + dst.endObject(); return true; } @@ -535,13 +542,14 @@ namespace LL template inline bool copy(const Value& src, std::vector& dst) { - if (src.is_array()) + simdjson::dom::array arr; + if (src.get_array().get(arr) == simdjson::SUCCESS) { - const boost::json::array& arr = src.get_array(); dst.resize(arr.size()); - for (size_t i = 0; i < arr.size(); ++i) + size_t i = 0; + for (const Value& v : arr) { - copy(arr[i], dst[i]); + copy(v, dst[i++]); } return true; } @@ -550,47 +558,35 @@ namespace LL } template - inline bool write(const std::vector& src, Value& dst) + inline bool write(const std::vector& src, JsonWriter& dst) { - boost::json::array arr; + dst.startArray(); for (const T& t : src) { - Value v; - if (write(t, v)) - { - arr.push_back(v); - } - else - { - return false; - } + write(t, dst); } - dst = arr; + dst.endArray(); return true; } // to/from object member template - inline bool copy(const boost::json::object& src, string_view member, T& dst) + inline bool copy(const simdjson::dom::object& src, string_view member, T& dst) { - auto it = src.find(member); - if (it != src.end()) + Value v; + if (src[member].get(v) == simdjson::SUCCESS) { - return copy(it->value(), dst); + return copy(v, dst); } return false; } // always write a member to an object without checking default template - inline bool write_always(const T& src, string_view member, boost::json::object& dst) + inline bool write_always(const T& src, string_view member, JsonWriter& dst) { - Value& v = dst[member]; - if (!write(src, v)) - { - dst.erase(member); - return false; - } + dst.key(member); + write(src, dst); return true; } @@ -599,11 +595,12 @@ namespace LL // for internal use only, use copy_extensions instead template - inline bool _copy_extension(const boost::json::object& extensions, std::string_view member, T* dst) + inline bool _copy_extension(const simdjson::dom::object& extensions, std::string_view member, T* dst) { - if (extensions.contains(member)) + Value v; + if (extensions[member].get(v) == simdjson::SUCCESS) { - return copy(extensions.at(member), *dst); + return copy(v, *dst); } return false; @@ -616,50 +613,57 @@ namespace LL // "KHR_materials_pbrSpecularGlossiness", &mPbrSpecularGlossiness); // returns true if any of the extensions are copied template - inline bool copy_extensions(const boost::json::value& src, Types... args) + inline bool copy_extensions(const Value& src, Types... args) { // extract the extensions object (don't assume it exists and verify that it is an object) - if (src.is_object()) + simdjson::dom::object obj; + if (src.get_object().get(obj) == simdjson::SUCCESS) { - boost::json::object obj = src.get_object(); - if (obj.contains("extensions")) + simdjson::dom::object ext_obj; + if (obj["extensions"].get_object().get(ext_obj) == simdjson::SUCCESS) { - const boost::json::value& extensions = obj.at("extensions"); - if (extensions.is_object()) + bool success = false; + // copy each extension, return true if any of them succeed, do not short circuit on success + U32 count = sizeof...(args); + for (U32 i = 0; i < count; i += 2) { - const boost::json::object& ext_obj = extensions.as_object(); - bool success = false; - // copy each extension, return true if any of them succeed, do not short circuit on success - U32 count = sizeof...(args); - for (U32 i = 0; i < count; i += 2) + if (_copy_extension(ext_obj, args...)) { - if (_copy_extension(ext_obj, args...)) - { - success = true; - } + success = true; } - return success; } + return success; } } return false; } - // internal use aonly, use write_extensions instead - template - inline bool _write_extension(boost::json::object& extensions, const T* src, string_view member) + // internal use only, use write_extensions instead + inline bool _any_extension_present() + { + return false; + } + + template + inline bool _any_extension_present(const T* src, string_view member, Rest... rest) + { + return src->mPresent || _any_extension_present(rest...); + } + + inline void _write_extension(JsonWriter& dst) + { + } + + template + inline void _write_extension(JsonWriter& dst, const T* src, string_view member, Rest... rest) { if (src->mPresent) { - Value v; - if (write(*src, v)) - { - extensions[member] = v; - return true; - } + dst.key(member); + write(*src, dst); } - return false; + _write_extension(dst, rest...); } // Write all extensions to dst.extensions @@ -669,33 +673,25 @@ namespace LL // mPbrSpecularGlossiness, "KHR_materials_pbrSpecularGlossiness"); // returns true if any of the extensions are written template - inline bool write_extensions(boost::json::object& dst, Types... args) + inline bool write_extensions(JsonWriter& dst, Types... args) { - bool success = false; - - boost::json::object extensions; - U32 count = sizeof...(args) - 1; - - for (U32 i = 0; i < count; i += 2) + if (!_any_extension_present(args...)) { - if (_write_extension(extensions, args...)) - { - success = true; - } + return false; } - if (success) - { - dst["extensions"] = extensions; - } + dst.key("extensions"); + dst.startObject(); + _write_extension(dst, args...); + dst.endObject(); - return success; + return true; } // conditionally write a member to an object if the member // is not the default value template - inline bool write(const T& src, string_view member, boost::json::object& dst, const T& default_value = T()) + inline bool write(const T& src, string_view member, JsonWriter& dst, const T& default_value = T()) { if (src != default_value) { @@ -705,31 +701,25 @@ namespace LL } template - inline bool write(const std::unordered_map& src, string_view member, boost::json::object& dst, const std::unordered_map& default_value = std::unordered_map()) + inline bool write(const std::unordered_map& src, string_view member, JsonWriter& dst, const std::unordered_map& default_value = std::unordered_map()) { if (!src.empty()) { - Value v; - if (write(src, v)) - { - dst[member] = v; - return true; - } + dst.key(member); + write(src, dst); + return true; } return false; } template - inline bool write(const std::vector& src, string_view member, boost::json::object& dst, const std::vector& deafault_value = std::vector()) + inline bool write(const std::vector& src, string_view member, JsonWriter& dst, const std::vector& deafault_value = std::vector()) { if (!src.empty()) { - Value v; - if (write(src, v)) - { - dst[member] = v; - return true; - } + dst.key(member); + write(src, dst); + return true; } return false; } @@ -737,9 +727,9 @@ namespace LL template inline bool copy(const Value& src, string_view member, T& dst) { - if (src.is_object()) + simdjson::dom::object obj; + if (src.get_object().get(obj) == simdjson::SUCCESS) { - const boost::json::object& obj = src.as_object(); return copy(obj, member, dst); } @@ -750,18 +740,19 @@ namespace LL template<> inline bool copy(const Value& src, Accessor::ComponentType& dst) { - if (src.is_int64()) + int64_t i; + if (src.get_int64().get(i) == simdjson::SUCCESS) { - dst = (Accessor::ComponentType)src.get_int64(); + dst = (Accessor::ComponentType)i; return true; } return false; } template<> - inline bool write(const Accessor::ComponentType& src, Value& dst) + inline bool write(const Accessor::ComponentType& src, JsonWriter& dst) { - dst = (S32)src; + dst.value((S32)src); return true; } @@ -769,18 +760,19 @@ namespace LL template<> inline bool copy(const Value& src, Primitive::Mode& dst) { - if (src.is_int64()) + int64_t i; + if (src.get_int64().get(i) == simdjson::SUCCESS) { - dst = (Primitive::Mode)src.get_int64(); + dst = (Primitive::Mode)i; return true; } return false; } template<> - inline bool write(const Primitive::Mode& src, Value& dst) + inline bool write(const Primitive::Mode& src, JsonWriter& dst) { - dst = (S32)src; + dst.value((S32)src); return true; } @@ -788,21 +780,16 @@ namespace LL template<> inline bool copy(const Value& src, vec4& dst) { - if (src.is_array()) + simdjson::dom::array arr; + if (src.get_array().get(arr) == simdjson::SUCCESS && arr.size() == 4) { - const boost::json::array& arr = src.as_array(); - if (arr.size() == 4) + vec4 v; + if (to_float(arr.at(0).value_unsafe(), v.x) && + to_float(arr.at(1).value_unsafe(), v.y) && + to_float(arr.at(2).value_unsafe(), v.z) && + to_float(arr.at(3).value_unsafe(), v.w)) { - vec4 v; - std::error_code ec; - - v.x = arr[0].to_number(ec); if (ec) return false; - v.y = arr[1].to_number(ec); if (ec) return false; - v.z = arr[2].to_number(ec); if (ec) return false; - v.w = arr[3].to_number(ec); if (ec) return false; - dst = v; - return true; } } @@ -810,15 +797,14 @@ namespace LL } template<> - inline bool write(const vec4& src, Value& dst) - { - dst = boost::json::array(); - boost::json::array& arr = dst.get_array(); - arr.resize(4); - arr[0] = src.x; - arr[1] = src.y; - arr[2] = src.z; - arr[3] = src.w; + inline bool write(const vec4& src, JsonWriter& dst) + { + dst.startArray(); + dst.value(src.x); + dst.value(src.y); + dst.value(src.z); + dst.value(src.w); + dst.endArray(); return true; } @@ -826,17 +812,16 @@ namespace LL template<> inline bool copy(const Value& src, quat& dst) { - if (src.is_array()) + simdjson::dom::array arr; + if (src.get_array().get(arr) == simdjson::SUCCESS && arr.size() == 4) { - const boost::json::array& arr = src.as_array(); - if (arr.size() == 4) + quat q; + if (to_float(arr.at(0).value_unsafe(), q.x) && + to_float(arr.at(1).value_unsafe(), q.y) && + to_float(arr.at(2).value_unsafe(), q.z) && + to_float(arr.at(3).value_unsafe(), q.w)) { - std::error_code ec; - dst.x = arr[0].to_number(ec); if (ec) return false; - dst.y = arr[1].to_number(ec); if (ec) return false; - dst.z = arr[2].to_number(ec); if (ec) return false; - dst.w = arr[3].to_number(ec); if (ec) return false; - + dst = q; return true; } } @@ -844,15 +829,14 @@ namespace LL } template<> - inline bool write(const quat& src, Value& dst) - { - dst = boost::json::array(); - boost::json::array& arr = dst.get_array(); - arr.resize(4); - arr[0] = src.x; - arr[1] = src.y; - arr[2] = src.z; - arr[3] = src.w; + inline bool write(const quat& src, JsonWriter& dst) + { + dst.startArray(); + dst.value(src.x); + dst.value(src.y); + dst.value(src.z); + dst.value(src.w); + dst.endArray(); return true; } @@ -861,17 +845,14 @@ namespace LL template<> inline bool copy(const Value& src, vec3& dst) { - if (src.is_array()) + simdjson::dom::array arr; + if (src.get_array().get(arr) == simdjson::SUCCESS && arr.size() == 3) { - const boost::json::array& arr = src.as_array(); - if (arr.size() == 3) + vec3 t; + if (to_float(arr.at(0).value_unsafe(), t.x) && + to_float(arr.at(1).value_unsafe(), t.y) && + to_float(arr.at(2).value_unsafe(), t.z)) { - std::error_code ec; - vec3 t; - t.x = arr[0].to_number(ec); if (ec) return false; - t.y = arr[1].to_number(ec); if (ec) return false; - t.z = arr[2].to_number(ec); if (ec) return false; - dst = t; return true; } @@ -880,14 +861,13 @@ namespace LL } template<> - inline bool write(const vec3& src, Value& dst) - { - dst = boost::json::array(); - boost::json::array& arr = dst.as_array(); - arr.resize(3); - arr[0] = src.x; - arr[1] = src.y; - arr[2] = src.z; + inline bool write(const vec3& src, JsonWriter& dst) + { + dst.startArray(); + dst.value(src.x); + dst.value(src.y); + dst.value(src.z); + dst.endArray(); return true; } @@ -895,16 +875,13 @@ namespace LL template<> inline bool copy(const Value& src, vec2& dst) { - if (src.is_array()) + simdjson::dom::array arr; + if (src.get_array().get(arr) == simdjson::SUCCESS && arr.size() == 2) { - const boost::json::array& arr = src.as_array(); - if (arr.size() == 2) + vec2 t; + if (to_float(arr.at(0).value_unsafe(), t.x) && + to_float(arr.at(1).value_unsafe(), t.y)) { - std::error_code ec; - vec2 t; - t.x = arr[0].to_number(ec); if (ec) return false; - t.y = arr[1].to_number(ec); if (ec) return false; - dst = t; return true; } @@ -913,13 +890,12 @@ namespace LL } template<> - inline bool write(const vec2& src, Value& dst) + inline bool write(const vec2& src, JsonWriter& dst) { - dst = boost::json::array(); - boost::json::array& arr = dst.as_array(); - arr.resize(2); - arr[0] = src.x; - arr[1] = src.y; + dst.startArray(); + dst.value(src.x); + dst.value(src.y); + dst.endArray(); return true; } @@ -928,18 +904,19 @@ namespace LL template<> inline bool copy(const Value& src, bool& dst) { - if (src.is_bool()) + bool b; + if (src.get_bool().get(b) == simdjson::SUCCESS) { - dst = src.get_bool(); + dst = b; return true; } return false; } template<> - inline bool write(const bool& src, Value& dst) + inline bool write(const bool& src, JsonWriter& dst) { - dst = src; + dst.value(src); return true; } @@ -947,16 +924,13 @@ namespace LL template<> inline bool copy(const Value& src, F32& dst) { - std::error_code ec; - F32 t = src.to_number(ec); if (ec) return false; - dst = t; - return true; + return to_float(src, dst); } template<> - inline bool write(const F32& src, Value& dst) + inline bool write(const F32& src, JsonWriter& dst) { - dst = src; + dst.value(src); return true; } @@ -965,18 +939,19 @@ namespace LL template<> inline bool copy(const Value& src, U32& dst) { - if (src.is_int64()) + int64_t i; + if (src.get_int64().get(i) == simdjson::SUCCESS) { - dst = (U32)src.get_int64(); + dst = (U32)i; return true; } return false; } template<> - inline bool write(const U32& src, Value& dst) + inline bool write(const U32& src, JsonWriter& dst) { - dst = src; + dst.value(src); return true; } @@ -984,16 +959,19 @@ namespace LL template<> inline bool copy(const Value& src, F64& dst) { - std::error_code ec; - F64 t = src.to_number(ec); if (ec) return false; - dst = t; - return true; + double d; + if (src.get_double().get(d) == simdjson::SUCCESS) + { + dst = d; + return true; + } + return false; } template<> - inline bool write(const F64& src, Value& dst) + inline bool write(const F64& src, JsonWriter& dst) { - dst = src; + dst.value(src); return true; } @@ -1001,18 +979,19 @@ namespace LL template<> inline bool copy(const Value& src, Accessor::Type& dst) { - if (src.is_string()) + std::string_view sv; + if (src.get_string().get(sv) == simdjson::SUCCESS) { - dst = gltf_type_to_enum(src.get_string().c_str()); + dst = gltf_type_to_enum(std::string(sv)); return true; } return false; } template<> - inline bool write(const Accessor::Type& src, Value& dst) + inline bool write(const Accessor::Type& src, JsonWriter& dst) { - dst = enum_to_gltf_type(src); + dst.value(enum_to_gltf_type(src)); return true; } @@ -1020,18 +999,19 @@ namespace LL template<> inline bool copy(const Value& src, S32& dst) { - if (src.is_int64()) + int64_t i; + if (src.get_int64().get(i) == simdjson::SUCCESS) { - dst = (U32)src.get_int64(); + dst = (S32)i; return true; } return false; } template<> - inline bool write(const S32& src, Value& dst) + inline bool write(const S32& src, JsonWriter& dst) { - dst = src; + dst.value(src); return true; } @@ -1040,18 +1020,19 @@ namespace LL template<> inline bool copy(const Value& src, std::string& dst) { - if (src.is_string()) + std::string_view sv; + if (src.get_string().get(sv) == simdjson::SUCCESS) { - dst = src.get_string().c_str(); + dst = std::string(sv); return true; } return false; } template<> - inline bool write(const std::string& src, Value& dst) + inline bool write(const std::string& src, JsonWriter& dst) { - dst = src; + dst.value(src); return true; } @@ -1059,46 +1040,41 @@ namespace LL template<> inline bool copy(const Value& src, mat4& dst) { - if (src.is_array()) + simdjson::dom::array arr; + if (src.get_array().get(arr) == simdjson::SUCCESS && arr.size() == 16) { - const boost::json::array& arr = src.get_array(); - if (arr.size() == 16) + // populate a temporary local in case + // we hit an error in the middle of the array + // (don't partially write a matrix) + mat4 t; + F32* p = glm::value_ptr(t); + + U32 i = 0; + for (const Value& v : arr) { - // populate a temporary local in case - // we hit an error in the middle of the array - // (don't partially write a matrix) - mat4 t; - F32* p = glm::value_ptr(t); - - for (U32 i = 0; i < arr.size(); ++i) + if (!to_float(v, p[i++])) { - std::error_code ec; - p[i] = arr[i].to_number(ec); - if (ec) - { - return false; - } + return false; } - - dst = t; - return true; } + + dst = t; + return true; } return false; } template<> - inline bool write(const mat4& src, Value& dst) + inline bool write(const mat4& src, JsonWriter& dst) { - dst = boost::json::array(); - boost::json::array& arr = dst.get_array(); - arr.resize(16); + dst.startArray(); const F32* p = glm::value_ptr(src); for (U32 i = 0; i < 16; ++i) { - arr[i] = p[i]; + dst.value(p[i]); } + dst.endArray(); return true; } @@ -1106,18 +1082,19 @@ namespace LL template<> inline bool copy(const Value& src, Material::AlphaMode& dst) { - if (src.is_string()) + std::string_view sv; + if (src.get_string().get(sv) == simdjson::SUCCESS) { - dst = gltf_alpha_mode_to_enum(src.get_string().c_str()); + dst = gltf_alpha_mode_to_enum(std::string(sv)); return true; } return true; } template<> - inline bool write(const Material::AlphaMode& src, Value& dst) + inline bool write(const Material::AlphaMode& src, JsonWriter& dst) { - dst = enum_to_gltf_alpha_mode(src); + dst.value(enum_to_gltf_alpha_mode(src)); return true; } diff --git a/indra/newview/gltf/common.h b/indra/newview/gltf/common.h index 8cf3f1dff76..09c1a1fba17 100644 --- a/indra/newview/gltf/common.h +++ b/indra/newview/gltf/common.h @@ -34,7 +34,7 @@ #include "glm/ext/quaternion_float.hpp" #include "glm/gtx/quaternion.hpp" #include "glm/gtx/matrix_decompose.hpp" -#include +#include // Common types and constants used in the GLTF implementation namespace LL @@ -43,7 +43,89 @@ namespace LL { constexpr S32 INVALID_INDEX = -1; - using Value = boost::json::value; + // JSON documents are parsed with simdjson; Value is a read-only handle + // into a parsed document. Serialization streams JSON text through + // JsonWriter below. + using Value = simdjson::dom::element; + + // Streaming JSON writer over simdjson's string_builder. Handles + // comma/colon placement; serialize() implementations emit members + // in call order. + class JsonWriter + { + public: + void startObject() + { + comma(); + mBuilder.start_object(); + mNeedComma = false; + } + + void endObject() + { + mBuilder.end_object(); + mNeedComma = true; + } + + void startArray() + { + comma(); + mBuilder.start_array(); + mNeedComma = false; + } + + void endArray() + { + mBuilder.end_array(); + mNeedComma = true; + } + + // append "name": leaving the writer expecting the value + void key(std::string_view name) + { + comma(); + mBuilder.escape_and_append_with_quotes(name); + mBuilder.append_colon(); + mNeedComma = false; + } + + template::value>::type> + void value(T v) + { + comma(); + mBuilder.append(v); + mNeedComma = true; + } + + void value(std::string_view v) + { + comma(); + mBuilder.escape_and_append_with_quotes(v); + mNeedComma = true; + } + + std::string str() const + { + std::string_view view; + if (mBuilder.view().get(view) != simdjson::SUCCESS) + { + return {}; + } + return std::string(view); + } + + private: + void comma() + { + if (mNeedComma) + { + mBuilder.append_comma(); + } + } + + simdjson::builder::string_builder mBuilder; + bool mNeedComma = false; + }; using mat4 = glm::mat4; using vec4 = glm::vec4; diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index 388a6eee010..e9bb15fe993 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -36,7 +36,6 @@ using namespace LL::GLTF; -using namespace boost::json; // Mesh data useful for Mikktspace tangent generation (and flat normal generation) @@ -788,7 +787,7 @@ LLRender::eGeomModes gltf_mode_to_gl_mode(Primitive::Mode mode) } } -void Primitive::serialize(boost::json::object& dst) const +void Primitive::serialize(JsonWriter& dst) const { write(mMaterial, "material", dst, -1); write(mMode, "mode", dst, Primitive::Mode::TRIANGLES); diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h index 304eb264323..5a106ede0a0 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -28,14 +28,13 @@ #include "llvertexbuffer.h" #include "llvolumeoctree.h" -#include "boost/json.hpp" +#include "common.h" // LL GLTF Implementation namespace LL { namespace GLTF { - using Value = boost::json::value; class Asset; class Primitive @@ -104,7 +103,7 @@ namespace LL LLVector4a* tangent = NULL // return the surface tangent at the intersection point ); - void serialize(boost::json::object& obj) const; + void serialize(JsonWriter& obj) const; const Primitive& operator=(const Value& src); bool prep(Asset& asset); diff --git a/indra/newview/licenses-linux.txt b/indra/newview/licenses-linux.txt index a199ed410c6..a36551dde2d 100644 --- a/indra/newview/licenses-linux.txt +++ b/indra/newview/licenses-linux.txt @@ -632,3 +632,31 @@ supporting the PNG file format in commercial products. If you use this source code in a product, acknowledgment is not required but would be appreciated. + + +======== +simdjson +======== +simdjson is licensed under the Apache License, Version 2.0, or the MIT License, at your option. + +MIT License + +Copyright 2018-2025 The simdjson authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/indra/newview/licenses-mac.txt b/indra/newview/licenses-mac.txt index 0bd9ebc5f1f..99ea210c78b 100644 --- a/indra/newview/licenses-mac.txt +++ b/indra/newview/licenses-mac.txt @@ -593,3 +593,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======== +simdjson +======== +simdjson is licensed under the Apache License, Version 2.0, or the MIT License, at your option. + +MIT License + +Copyright 2018-2025 The simdjson authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/indra/newview/licenses-win32.txt b/indra/newview/licenses-win32.txt index b56f1bf2eb7..da66bc3d035 100644 --- a/indra/newview/licenses-win32.txt +++ b/indra/newview/licenses-win32.txt @@ -667,3 +667,31 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======== +simdjson +======== +simdjson is licensed under the Apache License, Version 2.0, or the MIT License, at your option. + +MIT License + +Copyright 2018-2025 The simdjson authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 03cbd6e8ea7..c7b7167e622 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -84,7 +84,7 @@ // Bugsplat (http://bugsplat.com) crash reporting tool #ifdef LL_BUGSPLAT #include "bugsplat/BugSplat.h" -#include "boost/json.hpp" // Boost.Json +#include #include "llagent.h" // for agent location #include "llstartup.h" #include "llviewerregion.h" @@ -745,24 +745,26 @@ bool LLAppViewerWin32::init() } else { - boost::system::error_code ec; - boost::json::value build_data = boost::json::parse(inf, ec); - if(ec.failed()) + std::string json((std::istreambuf_iterator(inf)), std::istreambuf_iterator()); + simdjson::dom::parser parser; + simdjson::dom::element build_data; + simdjson::error_code err = parser.parse(json).get(build_data); + if (err != simdjson::SUCCESS) { - // gah, the typo is baked into Json::Reader API LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't parse '" << build_data_fname - << "': " << ec.what() << LL_ENDL; + << "': " << simdjson::error_message(err) << LL_ENDL; } else { - if (!build_data.is_object() || !build_data.as_object().contains("BugSplat DB")) + std::string_view bugsplat_db; + if (build_data["BugSplat DB"].get_string().get(bugsplat_db) != simdjson::SUCCESS) { LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, no 'BugSplat DB' entry in '" << build_data_fname << "'" << LL_ENDL; } else { - boost::json::value BugSplat_DB = build_data.at("BugSplat DB"); + std::string BugSplat_DB(bugsplat_db); // Got BugSplat_DB, onward! std::wstring version_string(WSTRINGIZE(LL_VIEWER_VERSION_MAJOR << '.' << @@ -785,7 +787,7 @@ bool LLAppViewerWin32::init() // have to convert normal wide strings to strings of __wchar_t sBugSplatSender = new MiniDmpSender( - WCSTR(boost::json::value_to(BugSplat_DB)), + WCSTR(BugSplat_DB), WCSTR(LL_TO_WSTRING(LL_VIEWER_CHANNEL)), WCSTR(version_string), nullptr, // szAppIdentifier -- set later diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 562aa920de8..f906c00945d 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -124,7 +124,7 @@ #include "llpresetsmanager.h" #include "llinventoryfunctions.h" -#include +#include #include #include "llsearchableui.h" @@ -651,6 +651,17 @@ void LLFloaterPreference::onAutoRespondNonFriendsResponseChanged() //////////////////////////////////////////////////// // Skins panel +// extract a string member from a manifest object, with a fallback +static std::string manifest_member(const simdjson::dom::object& obj, std::string_view key, const std::string& fallback) +{ + std::string_view value; + if (obj[key].get_string().get(value) == simdjson::SUCCESS) + { + return std::string(value); + } + return fallback; +} + skin_t manifestFromJson(const std::string& filename, const ESkinType type) { skin_t skin; @@ -658,17 +669,19 @@ skin_t manifestFromJson(const std::string& filename, const ESkinType type) in.open(filename); if (in.is_open()) { - boost::system::error_code ec; - auto root = boost::json::parse(in, ec); - if (!ec.failed() && root.is_object()) + std::string json((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + simdjson::dom::parser parser; + simdjson::dom::object jobj; + simdjson::error_code err = parser.parse(json).get_object().get(jobj); + if (err == simdjson::SUCCESS) { - auto jobj = root.as_object(); - skin.mName = jobj.contains("name") ? boost::json::value_to(jobj.at("name")) : "Unknown"; - skin.mAuthor = jobj.contains("author") ? boost::json::value_to(jobj.at("author")) : LLTrans::getString("Unknown"); - skin.mUrl = jobj.contains("url") ? boost::json::value_to(jobj.at("url")) : LLTrans::getString("Unknown"); - skin.mCompatVer = jobj.contains("compatibility") ? boost::json::value_to(jobj.at("compatibility")) : LLTrans::getString("Unknown"); - skin.mDate = jobj.contains("date") ? LLDate(boost::json::value_to(jobj.at("date"))) : LLDate::now(); - skin.mNotes = jobj.contains("notes") ? boost::json::value_to(jobj.at("notes")) : ""; + skin.mName = manifest_member(jobj, "name", "Unknown"); + skin.mAuthor = manifest_member(jobj, "author", LLTrans::getString("Unknown")); + skin.mUrl = manifest_member(jobj, "url", LLTrans::getString("Unknown")); + skin.mCompatVer = manifest_member(jobj, "compatibility", LLTrans::getString("Unknown")); + std::string_view date_view; + skin.mDate = (jobj["date"].get_string().get(date_view) == simdjson::SUCCESS) ? LLDate(std::string(date_view)) : LLDate::now(); + skin.mNotes = manifest_member(jobj, "notes", ""); // If it's a system skin, the compatability version is always the current build if (type == SYSTEM_SKIN) { @@ -677,7 +690,7 @@ skin_t manifestFromJson(const std::string& filename, const ESkinType type) } else { - LL_WARNS() << "Failed to parse " << filename << ": " << ec.message() << LL_ENDL; + LL_WARNS() << "Failed to parse " << filename << ": " << simdjson::error_message(err) << LL_ENDL; } in.close(); } @@ -778,16 +791,14 @@ void LLFloaterPreference::onAddSkinCallback(const std::vector& file auto buf = std::make_unique(buf_size); zip->extractFile("manifest.json", buf.get(), buf_size); buf[buf_size - 1] = '\0'; // force. - std::stringstream ss; - ss << std::string(const_cast(buf.get()), buf_size); + std::string json(buf.get()); buf.reset(); - boost::system::error_code ec; - auto root = boost::json::parse(ss, ec); - if (!ec.failed() && root.is_object()) + simdjson::dom::parser parser; + simdjson::dom::object jobj; + if (parser.parse(json).get_object().get(jobj) == simdjson::SUCCESS) { - const auto& jobj = root.as_object(); - const std::string& name = jobj.contains("name") ? boost::json::value_to(jobj.at("name")) : "Unknown"; + const std::string name = manifest_member(jobj, "name", "Unknown"); std::string pathname = gDirUtilp->add(gDirUtilp->getOSUserAppDir(), "skins"); if (!gDirUtilp->fileExists(pathname)) { diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index dcd1582eb9b..1ffedc9103b 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -40,7 +40,17 @@ #include "llurlregistry.h" #include "stringize.h" -#include +#include + +namespace +{ + // reused parse buffers; responses are fully consumed before the next parse + simdjson::dom::parser& translationParser() + { + thread_local simdjson::dom::parser parser; + return parser; + } +} static const std::string AZURE_NOTRANSLATE_OPENING_TAG("
"); static const std::string AZURE_NOTRANSLATE_CLOSING_TAG("
"); @@ -344,11 +354,11 @@ class LLGoogleTranslationHandler : public LLTranslationAPIHandler private: static void parseErrorResponse( - const boost::json::value& root, + const simdjson::dom::element& root, int& status, std::string& err_msg); static bool parseTranslation( - const boost::json::value& root, + const simdjson::dom::element& root, std::string& translation, std::string& detected_lang); static std::string getAPIKey(); @@ -398,11 +408,11 @@ bool LLGoogleTranslationHandler::parseResponse( { const std::string& text = !body.empty() ? body : http_response["error_body"].asStringRef(); - boost::system::error_code ec; - boost::json::value root = boost::json::parse(text, ec); - if (ec.failed()) + simdjson::dom::element root; + simdjson::error_code err = translationParser().parse(text.data(), text.size()).get(root); + if (err != simdjson::SUCCESS) { - err_msg = ec.what(); + err_msg = simdjson::error_message(err); return false; } @@ -427,53 +437,45 @@ bool LLGoogleTranslationHandler::isConfigured() const // static void LLGoogleTranslationHandler::parseErrorResponse( - const boost::json::value& root, + const simdjson::dom::element& root, int& status, std::string& err_msg) { - boost::system::error_code ec; - auto message = root.find_pointer("/data/message", ec); - auto code = root.find_pointer("/data/code", ec); - if (!message || !code) - { - return; - } - - auto message_val = boost::json::try_value_to(*message); - auto code_val = boost::json::try_value_to(*code); - if (!message_val || !code_val) + std::string_view message; + int64_t code; + if (root.at_pointer("/data/message").get_string().get(message) != simdjson::SUCCESS || + root.at_pointer("/data/code").get_int64().get(code) != simdjson::SUCCESS) { return; } - err_msg = message_val.value(); - status = code_val.value(); + err_msg = message; + status = (int)code; } // static bool LLGoogleTranslationHandler::parseTranslation( - const boost::json::value& root, + const simdjson::dom::element& root, std::string& translation, std::string& detected_lang) { - boost::system::error_code ec; - auto translated_text = root.find_pointer("/data/translations/0/translatedText", ec); - if (!translated_text) return false; + auto translated_text = root.at_pointer("/data/translations/0/translatedText"); + if (translated_text.error() != simdjson::SUCCESS) return false; - auto text_val = boost::json::try_value_to(*translated_text); - if (!text_val) + std::string_view text_view; + if (translated_text.get_string().get(text_view) != simdjson::SUCCESS) { - LL_WARNS() << "Failed to parse translation" << text_val.error() << LL_ENDL; + LL_WARNS() << "Failed to parse translation" << LL_ENDL; return false; } - translation = text_val.value(); + translation = text_view; - auto language = root.find_pointer("/data/translations/0/detectedSourceLanguage", ec); - if (language) + auto language = root.at_pointer("/data/translations/0/detectedSourceLanguage"); + if (language.error() == simdjson::SUCCESS) { - auto lang_val = boost::json::try_value_to(*language); - detected_lang = lang_val ? lang_val.value() : ""; + std::string_view lang_view; + detected_lang = (language.get_string().get(lang_view) == simdjson::SUCCESS) ? std::string(lang_view) : std::string(); } return true; @@ -656,11 +658,12 @@ bool LLAzureTranslationHandler::checkVerificationResponse( // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" // But for now just verify response is a valid json - boost::system::error_code ec; - boost::json::value root = boost::json::parse(response["error_body"].asString(), ec); - if (ec.failed()) + const std::string& error_body = response["error_body"].asStringRef(); + simdjson::dom::element root; + simdjson::error_code err = translationParser().parse(error_body.data(), error_body.size()).get(root); + if (err != simdjson::SUCCESS) { - LL_DEBUGS("Translate") << "Failed to parse error_body:" << ec.what() << LL_ENDL; + LL_DEBUGS("Translate") << "Failed to parse error_body:" << simdjson::error_message(err) << LL_ENDL; return false; } @@ -686,29 +689,30 @@ bool LLAzureTranslationHandler::parseResponse( //Example: // "[{\"detectedLanguage\":{\"language\":\"en\",\"score\":1.0},\"translations\":[{\"text\":\"Hello, what is your name?\",\"to\":\"en\"}]}]" - boost::system::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) + simdjson::dom::element root; + simdjson::error_code err = translationParser().parse(body.data(), body.size()).get(root); + if (err != simdjson::SUCCESS) { - err_msg = ec.what(); + err_msg = simdjson::error_message(err); return false; } - auto language = root.find_pointer("/0/detectedLanguage/language", ec); - if (!language) return false; - auto translated_text = root.find_pointer("/0/translations/0/text", ec); - if (!translated_text) return false; + auto language = root.at_pointer("/0/detectedLanguage/language"); + if (language.error() != simdjson::SUCCESS) return false; - auto lang_val = boost::json::try_value_to(*language); - auto text_val = boost::json::try_value_to(*translated_text); - if (!lang_val || !text_val) + auto translated_text = root.at_pointer("/0/translations/0/text"); + if (translated_text.error() != simdjson::SUCCESS) return false; + + std::string_view lang_view, text_view; + if (language.get_string().get(lang_view) != simdjson::SUCCESS || + translated_text.get_string().get(text_view) != simdjson::SUCCESS) { - LL_WARNS() << "Failed to parse translation" << lang_val.error() << text_val.error() << LL_ENDL; + LL_WARNS() << "Failed to parse translation" << LL_ENDL; return false; } - detected_lang = lang_val.value(); - translation = text_val.value(); + detected_lang = lang_view; + translation = text_view; return true; } @@ -726,25 +730,18 @@ std::string LLAzureTranslationHandler::parseErrorResponse( // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" // But for now just verify response is a valid json with an error - boost::system::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - return {}; - } - - auto err_msg = root.find_pointer("/error/message", ec); - if (!err_msg) + simdjson::dom::element root; + if (translationParser().parse(body.data(), body.size()).get(root) != simdjson::SUCCESS) { return {}; } - auto err_msg_val = boost::json::try_value_to(*err_msg); - if (!err_msg_val) + std::string_view err_msg; + if (root.at_pointer("/error/message").get_string().get(err_msg) != simdjson::SUCCESS) { return {}; } - return err_msg_val.value(); + return std::string(err_msg); } // static @@ -956,39 +953,39 @@ bool LLDeepLTranslationHandler::parseResponse( //Example: // "{\"translations\":[{\"detected_source_language\":\"EN\",\"text\":\"test\"}]}" - boost::system::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) + simdjson::dom::element root; + simdjson::error_code err = translationParser().parse(body.data(), body.size()).get(root); + if (err != simdjson::SUCCESS) { - err_msg = ec.message(); + err_msg = simdjson::error_message(err); return false; } - auto detected_langp = root.find_pointer("/translations/0/detected_source_language", ec); - if (!detected_langp || ec.failed()) // empty response? should not happen + auto detected_langp = root.at_pointer("/translations/0/detected_source_language"); + if (detected_langp.error() != simdjson::SUCCESS) // empty response? should not happen { - err_msg = ec.message(); + err_msg = "Unexpected response format"; return false; } // Request succeeded, extract translation from the response. - auto text_valp = root.find_pointer("/translations/0/text", ec); - if (!text_valp || ec.failed()) + auto text_valp = root.at_pointer("/translations/0/text"); + if (text_valp.error() != simdjson::SUCCESS) { - err_msg = ec.message(); + err_msg = "Unexpected response format"; return false; } - auto lang_result = boost::json::try_value_to(*detected_langp); - auto text_result = boost::json::try_value_to(*text_valp); - if (!lang_result || !text_result) + std::string_view lang_view, text_view; + if (detected_langp.get_string().get(lang_view) != simdjson::SUCCESS || + text_valp.get_string().get(text_view) != simdjson::SUCCESS) { return false; } - detected_lang = lang_result.value(); + detected_lang = lang_view; LLStringUtil::toLower(detected_lang); - translation = text_result.value(); + translation = text_view; return true; } @@ -1004,24 +1001,17 @@ std::string LLDeepLTranslationHandler::parseErrorResponse( const std::string& body) { // Example: "{\"message\":\"One of the request inputs is not valid.\"}" - boost::system::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - return {}; - } - - auto message_ptr = root.find_pointer("/message", ec); - if (!message_ptr || ec.failed()) + simdjson::dom::element root; + if (translationParser().parse(body.data(), body.size()).get(root) != simdjson::SUCCESS) { return {}; } - auto message_val = boost::json::try_value_to(*message_ptr); - if (!message_val) + std::string_view message; + if (root["message"].get_string().get(message) != simdjson::SUCCESS) return {}; - return message_val.value(); + return std::string(message); } // static diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 19907d52e92..272ed4ab595 100644 --- a/indra/newview/llvelopack.cpp +++ b/indra/newview/llvelopack.cpp @@ -35,7 +35,7 @@ #include "llcorehttputil.h" #include "llversioninfo.h" -#include +#include #include #include #include "llnotificationsutil.h" @@ -93,42 +93,103 @@ static std::string extract_basename(const std::string& url) return path; } -static void rewrite_asset_urls(boost::json::value& jv) +// re-emit the parsed feed verbatim, replacing absolute "FileName" URLs with +// their basenames as we stream +static void rewrite_asset_urls(const simdjson::dom::element& src, simdjson::builder::string_builder& dst) { - if (jv.is_object()) + switch (src.type()) { - auto& obj = jv.as_object(); - auto it = obj.find("FileName"); - if (it != obj.end() && it->value().is_string()) + case simdjson::dom::element_type::OBJECT: + { + dst.start_object(); + bool first = true; + for (const auto& member : src.get_object().value_unsafe()) { - std::string filename(it->value().as_string()); - if (filename.find("://") != std::string::npos) + if (!first) + { + dst.append_comma(); + } + first = false; + dst.escape_and_append_with_quotes(member.key); + dst.append_colon(); + + std::string_view filename; + if (member.key == "FileName" && + member.value.get_string().get(filename) == simdjson::SUCCESS && + filename.find("://") != std::string_view::npos) { - std::string basename = extract_basename(filename); - sAssetUrlMap[basename] = filename; - it->value() = basename; + std::string url(filename); + std::string basename = extract_basename(url); + sAssetUrlMap[basename] = url; + dst.escape_and_append_with_quotes(basename); LL_DEBUGS("Velopack") << "Rewrote FileName: " << basename << LL_ENDL; } + else + { + rewrite_asset_urls(member.value, dst); + } } - for (auto& kv : obj) - { - rewrite_asset_urls(kv.value()); - } + dst.end_object(); + break; } - else if (jv.is_array()) + case simdjson::dom::element_type::ARRAY: { - for (auto& elem : jv.as_array()) + dst.start_array(); + bool first = true; + for (const simdjson::dom::element& elem : src.get_array().value_unsafe()) { - rewrite_asset_urls(elem); + if (!first) + { + dst.append_comma(); + } + first = false; + rewrite_asset_urls(elem, dst); } + dst.end_array(); + break; + } + case simdjson::dom::element_type::STRING: + dst.escape_and_append_with_quotes(src.get_string().value_unsafe()); + break; + case simdjson::dom::element_type::INT64: + dst.append(src.get_int64().value_unsafe()); + break; + case simdjson::dom::element_type::UINT64: + dst.append(src.get_uint64().value_unsafe()); + break; + case simdjson::dom::element_type::DOUBLE: + dst.append(src.get_double().value_unsafe()); + break; + case simdjson::dom::element_type::BOOL: + dst.append(src.get_bool().value_unsafe()); + break; + case simdjson::dom::element_type::NULL_VALUE: + default: + dst.append_null(); + break; } } static std::string rewrite_release_feed(const std::string& json_str) { - boost::json::value jv = boost::json::parse(json_str); - rewrite_asset_urls(jv); - return boost::json::serialize(jv); + simdjson::dom::parser parser; + simdjson::dom::element root; + simdjson::error_code err = parser.parse(json_str).get(root); + if (err != simdjson::SUCCESS) + { + LL_WARNS("Velopack") << "Failed to parse release feed: " << simdjson::error_message(err) << LL_ENDL; + // Return original unmodified feed as fallback + return json_str; + } + + simdjson::builder::string_builder out; + rewrite_asset_urls(root, out); + std::string_view view; + if (out.view().get(view) != simdjson::SUCCESS) + { + return json_str; + } + return std::string(view); } static std::string download_url_raw(const std::string& url) diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index d11d17afbb6..677cdd495a0 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -88,8 +88,6 @@ #include "llworld.h" #include "pipeline.h" -#include - #include #include #include @@ -143,7 +141,7 @@ void render_ui_3d(); void render_ui_2d(); void render_disconnected_background(); -void getProfileStatsContext(boost::json::object& stats); +void getProfileStatsContext(LLSD& stats); std::string getProfileStatsFilename(); void display_startup() @@ -1085,9 +1083,8 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) if (gShaderProfileFrame) { gShaderProfileFrame = false; - boost::json::value stats; - stats.emplace_object(); - getProfileStatsContext(stats.as_object()); + LLSD stats = LLSD::emptyMap(); + getProfileStatsContext(stats); LLGLSLShader::finishProfile(stats); auto report_name = getProfileStatsFilename(); @@ -1098,36 +1095,35 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) } else { - outf << stats; + outf << LlsdToJson(stats); LL_INFOS() << "(also dumped to " << std::quoted(report_name) << ")" << LL_ENDL; } } } -void getProfileStatsContext(boost::json::object& stats) +void getProfileStatsContext(LLSD& stats) { // populate the context with info from LLFloaterAbout - auto contextit = stats.emplace("context", - LlsdToJson(LLAppViewer::instance()->getViewerInfo())).first; - auto& context = contextit->value().as_object(); + LLSD& context = stats["context"]; + context = LLAppViewer::instance()->getViewerInfo(); // then add a few more things unsigned char unique_id[MAC_ADDRESS_BYTES]{}; LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); - context.emplace("machine", stringize(LL::hexdump(unique_id, sizeof(unique_id)))); - context.emplace("grid", LLGridManager::instance().getGrid()); + context["machine"] = stringize(LL::hexdump(unique_id, sizeof(unique_id))); + context["grid"] = LLGridManager::instance().getGrid(); LLViewerRegion* region = gAgent.getRegion(); if (region) { - context.emplace("regionid", stringize(region->getRegionID())); + context["regionid"] = stringize(region->getRegionID()); } LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); if (parcel) { - context.emplace("parcel", parcel->getName()); - context.emplace("parcelid", parcel->getLocalID()); + context["parcel"] = parcel->getName(); + context["parcelid"] = parcel->getLocalID(); } - context.emplace("time", LLDate::now().toHTTPDateString("%Y-%m-%dT%H:%M:%S")); + context["time"] = LLDate::now().toHTTPDateString("%Y-%m-%dT%H:%M:%S"); } std::string getProfileStatsFilename() diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index d95b50a8454..5e203ce0311 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -146,7 +146,6 @@ #include #include #include -#include #include "llcleanup.h" #include "llviewershadermgr.h" // [RLVa:KB] - Checked: 2011-05-22 (RLVa-1.3.1a) diff --git a/indra/newview/llviewerprecompiledheaders.h b/indra/newview/llviewerprecompiledheaders.h index d33d4267693..feafbb35f54 100644 --- a/indra/newview/llviewerprecompiledheaders.h +++ b/indra/newview/llviewerprecompiledheaders.h @@ -126,7 +126,7 @@ #include "llfloater.h" #include -#include +#include #include "glm/glm.hpp" #include "glm/gtc/type_ptr.hpp" diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 60239486a43..db1852cdcd9 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -74,7 +74,7 @@ #include "llwebrtc.h" -#include "boost/json.hpp" +#include const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc"; @@ -106,6 +106,44 @@ namespace { const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); + // append "key":{"k0":v0,...} to a JSON object under construction + template + void json_append_keyed_object(simdjson::builder::string_builder& dst, std::string_view key, + const Fields& fields) + { + dst.escape_and_append_with_quotes(key); + dst.append_colon(); + dst.start_object(); + bool first = true; + for (const auto& field : fields) + { + if (!first) + { + dst.append_comma(); + } + first = false; + dst.append_key_value(std::string_view(field.first), field.second); + } + dst.end_object(); + } + + template + void json_append_keyed_object(simdjson::builder::string_builder& dst, std::string_view key, + std::initializer_list> fields) + { + json_append_keyed_object>>(dst, key, fields); + } + + std::string json_to_string(const simdjson::builder::string_builder& builder) + { + std::string_view view; + if (builder.view().get(view) != simdjson::SUCCESS) + { + return {}; + } + return std::string(view); + } + } // namespace @@ -1189,34 +1227,38 @@ void LLWebRTCVoiceClient::sendPositionUpdate(bool force) if (mSpatialCoordsDirty || force) { - boost::json::object spatial; + simdjson::builder::string_builder spatial; - spatial["sp"] = { + spatial.start_object(); + json_append_keyed_object(spatial, "sp", { {"x", (int) (mAvatarPosition[0] * 100)}, {"y", (int) (mAvatarPosition[1] * 100)}, {"z", (int) (mAvatarPosition[2] * 100)} - }; - spatial["sh"] = { + }); + spatial.append_comma(); + json_append_keyed_object(spatial, "sh", { {"x", (int) (mAvatarRot[0] * 100)}, {"y", (int) (mAvatarRot[1] * 100)}, {"z", (int) (mAvatarRot[2] * 100)}, {"w", (int) (mAvatarRot[3] * 100)} - }; - - spatial["lp"] = { + }); + spatial.append_comma(); + json_append_keyed_object(spatial, "lp", { {"x", (int) (mListenerPosition[0] * 100)}, {"y", (int) (mListenerPosition[1] * 100)}, {"z", (int) (mListenerPosition[2] * 100)} - }; - - spatial["lh"] = { + }); + spatial.append_comma(); + json_append_keyed_object(spatial, "lh", { {"x", (int) (mListenerRot[0] * 100)}, {"y", (int) (mListenerRot[1] * 100)}, {"z", (int) (mListenerRot[2] * 100)}, - {"w", (int) (mListenerRot[3] * 100)}}; + {"w", (int) (mListenerRot[3] * 100)} + }); + spatial.end_object(); mSpatialCoordsDirty = false; - spatial_data = boost::json::serialize(spatial); + spatial_data = json_to_string(spatial); sessionState::for_each(boost::bind(predSendData, _1, spatial_data)); } @@ -2647,21 +2689,25 @@ void LLVoiceWebRTCConnection::setSpeakerVolume(F32 volume) void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume) { - boost::json::object root = { { "ug", { { id.asString(), (uint32_t)(volume * PEER_GAIN_CONVERSION_FACTOR) } } } }; - std::string json_data = boost::json::serialize(root); + simdjson::builder::string_builder root; + root.start_object(); + json_append_keyed_object(root, "ug", { { id.asString(), (uint32_t)(volume * PEER_GAIN_CONVERSION_FACTOR) } }); + root.end_object(); if (mWebRTCDataInterface) { - mWebRTCDataInterface->sendData(json_data, false); + mWebRTCDataInterface->sendData(json_to_string(root), false); } } void LLVoiceWebRTCConnection::setUserMute(const LLUUID& id, bool mute) { - boost::json::object root = { { "m", { { id.asString(), mute } } } }; - std::string json_data = boost::json::serialize(root); + simdjson::builder::string_builder root; + root.start_object(); + json_append_keyed_object(root, "m", { { id.asString(), mute } }); + root.end_object(); if (mWebRTCDataInterface) { - mWebRTCDataInterface->sendData(json_data, false); + mWebRTCDataInterface->sendData(json_to_string(root), false); } } @@ -3143,11 +3189,12 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b return; } - boost::system::error_code ec; - boost::json::value voice_data_parsed = boost::json::parse(data, ec); - if (!ec) // don't collect comments + thread_local simdjson::dom::parser parser; + simdjson::dom::element voice_data_parsed; + if (parser.parse(data).get(voice_data_parsed) == simdjson::SUCCESS) { - if (!voice_data_parsed.is_object()) + simdjson::dom::object voice_data; + if (voice_data_parsed.get_object().get(voice_data) != simdjson::SUCCESS) { LL_WARNS("Voice") << "Expected object from data channel:" << data << LL_ENDL; return; @@ -3159,32 +3206,31 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b is_primary_region = (mRegionID == gAgent.getRegion()->getRegionID()); LL_WARNS() << "mPrimary is false, expected: " << is_primary_region << " connection state: " << getVoiceConnectionState() << LL_ENDL; } - boost::json::object voice_data = voice_data_parsed.as_object(); - boost::json::object mute; - boost::json::object user_gain; - for (auto &participant_elem : voice_data) + std::vector> mute; + std::vector> user_gain; + for (auto& participant_elem : voice_data) { - boost::json::string participant_id(participant_elem.key()); - LLUUID agent_id(participant_id.c_str()); + std::string participant_id(participant_elem.key); + LLUUID agent_id(participant_id); if (agent_id.isNull()) { // probably a test client. continue; } - if (!participant_elem.value().is_object()) + simdjson::dom::object participant_obj; + if (participant_elem.value.get_object().get(participant_obj) != simdjson::SUCCESS) { continue; } - boost::json::object participant_obj = participant_elem.value().as_object(); - - if (participant_obj.contains("V") && participant_obj["V"].is_string() && agent_id == gAgentID) + std::string_view server_version; + if (participant_obj["V"].get_string().get(server_version) == simdjson::SUCCESS && agent_id == gAgentID) { // sendJoin was called on the connection. The voice server has responded with the new version string. Set it here. - mServerVersion = participant_obj["V"].as_string().c_str(); + mServerVersion = std::string(server_version); LLWebRTCVoiceClient::getInstance()->updateVersion(); - LL_DEBUGS("Voice") << "Received version string \"" << participant_obj["V"].as_string().c_str() + LL_DEBUGS("Voice") << "Received version string \"" << mServerVersion << "\" for connection: primary=" << mPrimary << ", spatial=" << isSpatial() << ", region=" << mRegionID << ", mChannelID=" << mChannelID << LL_ENDL; } @@ -3198,27 +3244,27 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b // where a participant on a neighboring region may be // connected to multiple servers. We don't want to // add new identical participants from all of those servers. - if (participant_obj.contains("j") && - participant_obj["j"].is_object()) + simdjson::dom::object join_obj; + if (participant_obj["j"].get_object().get(join_obj) == simdjson::SUCCESS) { // a new participant has announced that they're joining. joined = true; - if (participant_elem.value().as_object()["j"].as_object().contains("p") && - participant_elem.value().as_object()["j"].as_object()["p"].is_bool()) + bool join_primary; + if (join_obj["p"].get_bool().get(join_primary) == simdjson::SUCCESS) { - primary = participant_elem.value().as_object()["j"].as_object()["p"].as_bool(); + primary = join_primary; } // track incoming participants that are muted so we can mute their connections (or set their volume) bool isMuted = LLMuteList::getInstance()->isMuted(agent_id, LLMute::flagVoiceChat); if (isMuted) { - mute[participant_id] = true; + mute.emplace_back(participant_id, true); } F32 volume; if(LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(agent_id, volume)) { - user_gain[participant_id] = (uint32_t)(volume * 200); + user_gain.emplace_back(participant_id, (uint32_t)(volume * 200)); } } @@ -3229,7 +3275,8 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b if (participant) { - if (participant_obj.contains("l") && participant_obj["l"].is_bool() && participant_obj["l"].as_bool()) + bool left; + if (participant_obj["l"].get_bool().get(left) == simdjson::SUCCESS && left) { // an existing participant is leaving. if (agent_id != gAgentID) @@ -3240,21 +3287,23 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b else { // we got a 'power' update. - if (participant_obj.contains("p") && participant_obj["p"].is_number()) + int64_t level; + if (participant_obj["p"].get_int64().get(level) == simdjson::SUCCESS) { // server sends up power as an integer which is level * 128 to save // character count. - participant->mLevel = (F32)participant_obj["p"].as_int64()/128.0f; + participant->mLevel = (F32)level/128.0f; } - if (participant_obj.contains("v") && participant_obj["v"].is_bool()) + bool is_speaking; + if (participant_obj["v"].get_bool().get(is_speaking) == simdjson::SUCCESS) { - participant->mIsSpeaking = participant_obj["v"].as_bool(); + participant->mIsSpeaking = is_speaking; } - if (participant_obj.contains("m") && participant_obj["m"].is_bool()) + bool is_moderator_muted; + if (participant_obj["m"].get_bool().get(is_moderator_muted) == simdjson::SUCCESS) { - bool is_moderator_muted = participant_obj["m"].as_bool(); if (isSpatial()) { // ignore muted flags from non-primary server @@ -3279,13 +3328,13 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b if (isSpatial() && (is_primary_region || primary)) { // mute info message can be received before join message, so try to mute again later - if (participant_obj.contains("m") && participant_obj["m"].is_bool()) + bool is_moderator_muted; + if (participant_obj["m"].get_bool().get(is_moderator_muted) == simdjson::SUCCESS) { - LL_WARNS() << "Mute info msg received: " << participant_obj["m"].as_bool() + LL_WARNS() << "Mute info msg received: " << is_moderator_muted << " but participant " << agent_id << " was not found in channel " << mChannelID << LL_ENDL; - bool is_moderator_muted = participant_obj["m"].as_bool(); std::string channel_id = mChannelID; F32 delay { 1.5f }; doAfterInterval( @@ -3313,19 +3362,24 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b } // tell the simulator to set the mute and volume data for this // participant, if there are any updates. - boost::json::object root; - if (mute.size() > 0) - { - root["m"] = mute; - } - if (user_gain.size() > 0) + if ((!mute.empty() || !user_gain.empty()) && mWebRTCDataInterface) { - root["ug"] = user_gain; - } - if (root.size() > 0 && mWebRTCDataInterface) - { - std::string json_data = boost::json::serialize(root); - mWebRTCDataInterface->sendData(json_data, false); + simdjson::builder::string_builder root; + root.start_object(); + if (!mute.empty()) + { + json_append_keyed_object(root, "m", mute); + } + if (!user_gain.empty()) + { + if (!mute.empty()) + { + root.append_comma(); + } + json_append_keyed_object(root, "ug", user_gain); + } + root.end_object(); + mWebRTCDataInterface->sendData(json_to_string(root), false); } } } @@ -3371,15 +3425,18 @@ void LLVoiceWebRTCConnection::sendJoin() return; } - boost::json::object root; - boost::json::object join_obj; + simdjson::builder::string_builder root; + root.start_object(); + root.escape_and_append_with_quotes("j"); + root.append_colon(); + root.start_object(); if (mPrimary) { - join_obj["p"] = true; + root.append_key_value("p", true); } - root["j"] = join_obj; - std::string json_data = boost::json::serialize(root); - mWebRTCDataInterface->sendData(json_data, false); + root.end_object(); + root.end_object(); + mWebRTCDataInterface->sendData(json_to_string(root), false); } void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& stats_data) diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h index c9f52da2084..9e838fc44c4 100644 --- a/indra/newview/llvoicewebrtc.h +++ b/indra/newview/llvoicewebrtc.h @@ -43,7 +43,6 @@ class LLWebRTCProtocolParser; #include "llparcel.h" #include "llmutelist.h" #include -#include "boost/json.hpp" #include #include "llvoiceclient.h" diff --git a/indra/vcpkg.json b/indra/vcpkg.json index f810efd5e1e..e2b4bec94b7 100644 --- a/indra/vcpkg.json +++ b/indra/vcpkg.json @@ -11,7 +11,6 @@ "boost-fiber", "boost-graph", "boost-iostreams", - "boost-json", "boost-program-options", "boost-random", "boost-regex", @@ -134,6 +133,7 @@ ] }, "secondlife-emoji-shortcodes", + "simdjson", "simdutf", { "name": "sse2neon", From b52d9862a515deaa30bf8c7b11bedee70b211648 Mon Sep 17 00:00:00 2001 From: Rye Date: Fri, 12 Jun 2026 04:43:02 -0400 Subject: [PATCH 2/2] Fix dangling simdjson_result iteration and address review feedback The Linux CI crash: range-for over get_object()/get_array() results called .value_unsafe() on the simdjson_result temporary, binding the loop range to a reference inside it. Range-for does not extend the inner temporary's lifetime (pre-C++23), so the handle dangled before begin() ran. MSVC's stack layout masked it; gcc -O2 segfaulted. Reproduced and verified the fix against simdjson 4.6.4 with gcc at -O2. Copy the dom::object/dom::array handle to a local before iterating (llsdjson.cpp object conversion, llvelopack feed rewrite). Review feedback (CodeRabbit): - Asset::serialize now emits copyright and the preserved extras subtree (raw JSON via new JsonWriter::rawValue). - Asset::save fails with a warning when the output file cannot be opened or written. - copy_extensions() recursively consumes its (member, dst) pairs, mirroring _write_extension, instead of the vestigial indexed loop. - Material::AlphaMode copy returns false on a non-string source. - LlsdFromJson BIGINT degrades to Real unconditionally (get_bigint digits via strtod when get_double cannot convert); note the branch is unreachable under the default parser configuration, which fails the parse with BIGINT_ERROR instead. - Google Translate error parsing reads /error/message and /error/code (the /data paths were inherited from the boost implementation but never matched the v2 REST error model). - llsdjson_test: regression for an oversized integer literal; README code fences tagged cpp. Co-Authored-By: Claude Fable 5 --- indra/llcommon/llsdjson.cpp | 23 ++++++++++++++++---- indra/llcommon/tests/llsdjson_test.cpp | 3 +++ indra/newview/gltf/README.md | 8 +++---- indra/newview/gltf/asset.cpp | 16 ++++++++++++++ indra/newview/gltf/buffer_util.h | 30 ++++++++++++-------------- indra/newview/gltf/common.h | 8 +++++++ indra/newview/lltranslate.cpp | 4 ++-- indra/newview/llvelopack.cpp | 9 ++++++-- 8 files changed, 73 insertions(+), 28 deletions(-) diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index ce4b1ed185d..6b794daa44a 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -34,6 +34,7 @@ #include "llerror.h" #include +#include //========================================================================= LLSD LlsdFromJson(const simdjson::dom::element& val) @@ -57,12 +58,21 @@ LLSD LlsdFromJson(const simdjson::dom::element& val) case simdjson::dom::element_type::BIGINT: { // integer too large for int64/uint64: degrade to Real, matching how - // such literals previously parsed as doubles + // 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) + if (val.get_double().get(bigval) != simdjson::SUCCESS) { - result = LLSD(bigval); + 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; } case simdjson::dom::element_type::STRING: @@ -89,13 +99,18 @@ LLSD LlsdFromJson(const simdjson::dom::element& val) break; } case simdjson::dom::element_type::OBJECT: + { result = LLSD::emptyMap(); - for (const simdjson::dom::key_value_pair& member : val.get_object().value_unsafe()) + // 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[member.key] = LlsdFromJson(member.value); } break; } + } return result; } diff --git a/indra/llcommon/tests/llsdjson_test.cpp b/indra/llcommon/tests/llsdjson_test.cpp index 77782b1b7d5..027d0c36bc8 100644 --- a/indra/llcommon/tests/llsdjson_test.cpp +++ b/indra/llcommon/tests/llsdjson_test.cpp @@ -253,6 +253,9 @@ namespace tut "'single quotes'", "{\"a\": 1,}", "\"unterminated", + // valid syntax but exceeds uint64: rejected with BIGINT_ERROR + // under the default parser configuration + "18446744073709551616", }; for (const char* json : bad) { diff --git a/indra/newview/gltf/README.md b/indra/newview/gltf/README.md index 25e79dc2c31..85ef64c92be 100644 --- a/indra/newview/gltf/README.md +++ b/indra/newview/gltf/README.md @@ -31,7 +31,7 @@ mutable document tree. Each class should provide two functions (Primitive shown for example): -``` +```cpp // Append "this" in json form to the provided writer // Do not serialize default values void serialize(JsonWriter& obj) const; @@ -42,7 +42,7 @@ const Primitive& operator=(const Value& src); "serialize" implementations should use "write": -``` +```cpp void Primitive::serialize(JsonWriter& dst) const { write(mMaterial, "material", dst, -1); @@ -54,7 +54,7 @@ void Primitive::serialize(JsonWriter& dst) const And operator= implementations should use "copy": -``` +```cpp const Primitive& Primitive::operator=(const Value& src) { if (src.is_object()) @@ -84,7 +84,7 @@ As implementers encounter new data types, you'll see compiler errors pointing at templates in buffer_util.h. See vec3 as a known good example of how to add support for a new type (there are bad examples, so beware): -``` +```cpp // vec3 template<> inline bool copy(const Value& src, vec3& dst) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index ed5f08fe160..f37fe557bac 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -898,6 +898,12 @@ void Asset::serialize(JsonWriter& dst) const write(mVersion, "version", dst); write(mMinVersion, "minVersion", dst, std::string()); write(sGenerator, "generator", dst); + write(mCopyright, "copyright", dst, std::string()); + if (!mExtras.empty()) + { + dst.key("extras"); + dst.rawValue(mExtras); + } dst.endObject(); write(mScene, "scene", dst, INVALID_INDEX); @@ -949,7 +955,17 @@ bool Asset::save(const std::string& filename) writer.endObject(); std::string buffer = writer.str(); llofstream file(filename, std::ios::binary); + if (!file.is_open()) + { + LL_WARNS("GLTF") << "Failed to open file for writing: " << filename << LL_ENDL; + return false; + } file.write(buffer.c_str(), buffer.size()); + if (!file.good()) + { + LL_WARNS("GLTF") << "Failed to write file: " << filename << LL_ENDL; + return false; + } return true; } diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index b7651467118..fcd8488a564 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -594,16 +594,24 @@ namespace LL // to/from extension // for internal use only, use copy_extensions instead - template - inline bool _copy_extension(const simdjson::dom::object& extensions, std::string_view member, T* dst) + inline bool _copy_extension(const simdjson::dom::object& extensions) + { + return false; + } + + template + inline bool _copy_extension(const simdjson::dom::object& extensions, std::string_view member, T* dst, Rest... rest) { + bool copied = false; Value v; if (extensions[member].get(v) == simdjson::SUCCESS) { - return copy(v, *dst); + copied = copy(v, *dst); } - return false; + // copy each extension; do not short circuit on success + bool rest_copied = _copy_extension(extensions, rest...); + return copied || rest_copied; } // Copy all extensions from src.extensions to provided destinations @@ -622,17 +630,7 @@ namespace LL simdjson::dom::object ext_obj; if (obj["extensions"].get_object().get(ext_obj) == simdjson::SUCCESS) { - bool success = false; - // copy each extension, return true if any of them succeed, do not short circuit on success - U32 count = sizeof...(args); - for (U32 i = 0; i < count; i += 2) - { - if (_copy_extension(ext_obj, args...)) - { - success = true; - } - } - return success; + return _copy_extension(ext_obj, args...); } } @@ -1088,7 +1086,7 @@ namespace LL dst = gltf_alpha_mode_to_enum(std::string(sv)); return true; } - return true; + return false; } template<> diff --git a/indra/newview/gltf/common.h b/indra/newview/gltf/common.h index 09c1a1fba17..2ee0743b213 100644 --- a/indra/newview/gltf/common.h +++ b/indra/newview/gltf/common.h @@ -104,6 +104,14 @@ namespace LL mNeedComma = true; } + // append pre-serialized JSON verbatim (e.g. a preserved extras subtree) + void rawValue(std::string_view json) + { + comma(); + mBuilder.append_raw(json); + mNeedComma = true; + } + std::string str() const { std::string_view view; diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 1ffedc9103b..b0d5074f488 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -443,8 +443,8 @@ void LLGoogleTranslationHandler::parseErrorResponse( { std::string_view message; int64_t code; - if (root.at_pointer("/data/message").get_string().get(message) != simdjson::SUCCESS || - root.at_pointer("/data/code").get_int64().get(code) != simdjson::SUCCESS) + if (root.at_pointer("/error/message").get_string().get(message) != simdjson::SUCCESS || + root.at_pointer("/error/code").get_int64().get(code) != simdjson::SUCCESS) { return; } diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 272ed4ab595..76418e6d062 100644 --- a/indra/newview/llvelopack.cpp +++ b/indra/newview/llvelopack.cpp @@ -103,7 +103,10 @@ static void rewrite_asset_urls(const simdjson::dom::element& src, simdjson::buil { dst.start_object(); bool first = true; - for (const auto& member : src.get_object().value_unsafe()) + // 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 = src.get_object().value_unsafe(); + for (const auto& member : object) { if (!first) { @@ -136,7 +139,9 @@ static void rewrite_asset_urls(const simdjson::dom::element& src, simdjson::buil { dst.start_array(); bool first = true; - for (const simdjson::dom::element& elem : src.get_array().value_unsafe()) + // see above: don't iterate the simdjson_result temporary + simdjson::dom::array array = src.get_array().value_unsafe(); + for (const simdjson::dom::element& elem : array) { if (!first) {