omni/structuredlog/JsonTree.h
File members: omni/structuredlog/JsonTree.h
// Copyright (c) 2020-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../../carb/logging/Log.h"
#include "../extras/ScratchBuffer.h"
#include <cstdint>
#include <cstddef>
namespace omni
{
namespace structuredlog
{
enum class NodeType : uint8_t
{
eNull,
eBool,
eBoolArray,
eInt32,
eInt32Array,
eUint32,
eUint32Array,
eInt64,
eInt64Array,
eUint64,
eUint64Array,
eFloat64,
eFloat64Array,
eFloat32,
eFloat32Array,
eBinary,
eString,
eStringArray,
eObject,
eObjectArray,
};
struct JsonNode
{
static constexpr uint32_t kVersion = 0;
using EnumBase = uint16_t;
using Flag = uint8_t;
static constexpr Flag fFlagConst = 0x01;
static constexpr Flag fFlagFixedLength = 0x02;
static constexpr Flag fFlagEnum = 0x04;
NodeType type = NodeType::eNull;
Flag flags = 0;
uint16_t len = 0;
uint16_t nameLen = 0;
/* 2 bytes of padding exists here (on x86_64 at least), which may be used
* for something in the future without breaking the ABI. */
char* name = nullptr;
union
{
bool boolVal;
int64_t intVal;
uint64_t uintVal;
double floatVal;
uint8_t* binaryVal;
char* strVal;
bool* boolArrayVal;
int32_t* int32ArrayVal;
int64_t* int64ArrayVal;
uint32_t* uint32ArrayVal;
uint64_t* uint64ArrayVal;
float* float32ArrayVal;
double* float64ArrayVal;
char** strArrayVal;
JsonNode* objVal;
} data;
};
CARB_ASSERT_INTEROP_SAFE(JsonNode);
static_assert(sizeof(JsonNode) == 24, "unexpected size");
static_assert(offsetof(JsonNode, type) == 0, "struct layout changed");
static_assert(offsetof(JsonNode, flags) == 1, "struct layout changed");
static_assert(offsetof(JsonNode, len) == 2, "struct layout changed");
static_assert(offsetof(JsonNode, nameLen) == 4, "struct layout changed");
static_assert(offsetof(JsonNode, name) == 8, "struct layout changed");
static_assert(offsetof(JsonNode, data) == 16, "struct layout changed");
enum class JsonTreeCompareFuzz
{
eStrict,
eNoConstOrder,
eNoOrder,
};
inline bool compareJsonTrees(const JsonNode* a, const JsonNode* b, JsonTreeCompareFuzz fuzz = JsonTreeCompareFuzz::eStrict)
{
if (a->flags != b->flags || a->type != b->type || a->len != b->len)
return false;
if ((a->name == nullptr && b->name != nullptr) || (a->name != nullptr && b->name == nullptr))
return false;
if (a->name != nullptr && strcmp(a->name, b->name) != 0)
return false;
switch (a->type)
{
case NodeType::eNull:
return true;
case NodeType::eBool:
return a->data.boolVal == b->data.boolVal;
case NodeType::eInt32:
case NodeType::eInt64:
return a->data.intVal == b->data.intVal;
case NodeType::eUint32:
case NodeType::eUint64:
return a->data.uintVal == b->data.uintVal;
case NodeType::eFloat32:
case NodeType::eFloat64:
return a->data.floatVal == b->data.floatVal;
case NodeType::eBoolArray:
return a->len == 0 || memcmp(b->data.int32ArrayVal, a->data.int32ArrayVal, a->len * sizeof(bool)) == 0;
case NodeType::eUint32Array:
return a->len == 0 || memcmp(b->data.uint32ArrayVal, a->data.uint32ArrayVal, a->len * sizeof(uint32_t)) == 0;
case NodeType::eInt32Array:
return a->len == 0 || memcmp(b->data.int32ArrayVal, a->data.int32ArrayVal, a->len * sizeof(int32_t)) == 0;
case NodeType::eFloat32Array:
return a->len == 0 || memcmp(b->data.float32ArrayVal, a->data.float32ArrayVal, a->len * sizeof(float)) == 0;
case NodeType::eInt64Array:
return a->len == 0 || memcmp(b->data.int64ArrayVal, a->data.int64ArrayVal, a->len * sizeof(int64_t)) == 0;
case NodeType::eUint64Array:
return a->len == 0 || memcmp(b->data.uint64ArrayVal, a->data.uint64ArrayVal, a->len * sizeof(uint64_t)) == 0;
case NodeType::eFloat64Array:
return a->len == 0 || memcmp(b->data.float64ArrayVal, a->data.float64ArrayVal, a->len * sizeof(double)) == 0;
case NodeType::eBinary:
case NodeType::eString:
return a->len == 0 || memcmp(b->data.binaryVal, a->data.binaryVal, a->len) == 0;
case NodeType::eStringArray:
for (uint16_t i = 0; i < a->len; i++)
{
if ((a->data.strArrayVal[i] == nullptr && b->data.strArrayVal[i] != nullptr) ||
(a->data.strArrayVal[i] != nullptr && b->data.strArrayVal[i] == nullptr))
return false;
if (a->data.strArrayVal[i] != nullptr && strcmp(a->data.strArrayVal[i], b->data.strArrayVal[i]) != 0)
return false;
}
return true;
case NodeType::eObject:
case NodeType::eObjectArray:
switch (fuzz)
{
case JsonTreeCompareFuzz::eStrict:
for (uint16_t i = 0; i < a->len; i++)
{
if (!compareJsonTrees(&a->data.objVal[i], &b->data.objVal[i]))
return false;
}
break;
case JsonTreeCompareFuzz::eNoConstOrder:
{
extras::ScratchBuffer<bool, 256> hits;
if (!hits.resize(a->len))
{
// shouldn't ever happen
fprintf(stderr, "failed to allocate %u bytes\n", a->len);
return false;
}
for (size_t i = 0; i < a->len; i++)
{
hits[i] = false;
}
// first compare the variable fields in order
for (uint16_t i = 0, j = 0; i < a->len; i++, j++)
{
// skip to the next non-constant member
while ((a->data.objVal[i].flags & JsonNode::fFlagConst) != 0)
{
i++;
}
if (i >= a->len)
break;
// skip to the next non-constant member
while ((b->data.objVal[j].flags & JsonNode::fFlagConst) != 0)
{
j++;
}
if (j >= b->len)
return false;
if (!compareJsonTrees(&a->data.objVal[i], &b->data.objVal[j]))
return false;
}
// compare the constants
for (uint16_t i = 0; i < a->len; i++)
{
if ((a->data.objVal[i].flags & JsonNode::fFlagConst) == 0)
continue;
for (uint16_t j = 0; j < a->len; j++)
{
if (!hits[j] && (b->data.objVal[j].flags & JsonNode::fFlagConst) != 0 &&
compareJsonTrees(&a->data.objVal[i], &b->data.objVal[j]))
{
hits[j] = true;
break;
}
}
}
for (uint16_t i = 0; i < a->len; i++)
{
if ((b->data.objVal[i].flags & JsonNode::fFlagConst) != 0 && !hits[i])
return false;
}
}
break;
case JsonTreeCompareFuzz::eNoOrder:
{
extras::ScratchBuffer<bool, 256> hits;
if (!hits.resize(a->len))
{
// shouldn't ever happen
fprintf(stderr, "failed to allocate %u bytes\n", a->len);
return false;
}
for (size_t i = 0; i < a->len; i++)
{
hits[i] = false;
}
for (uint16_t i = 0; i < a->len; i++)
{
for (uint16_t j = 0; j < a->len; j++)
{
if (!hits[j] && compareJsonTrees(&a->data.objVal[i], &b->data.objVal[j]))
{
hits[j] = true;
break;
}
}
}
for (uint16_t i = 0; i < a->len; i++)
{
if (!hits[i])
return false;
}
}
break;
}
return true;
}
return false;
}
class Allocator
{
public:
static constexpr size_t kAlignment = alignof(void*);
virtual ~Allocator()
{
}
virtual void* alloc(size_t size)
{
return malloc(size);
}
virtual void dealloc(void* mem)
{
free(mem);
}
static size_t fixupAlignment(size_t size)
{
if (size % Allocator::kAlignment != 0)
return size + (kAlignment - (size % Allocator::kAlignment));
else
return size;
}
};
class BlockAllocator : public Allocator
{
public:
BlockAllocator(void* block, size_t len)
{
m_block = static_cast<uint8_t*>(block);
m_left = len;
}
void* alloc(size_t size) override
{
void* m = m_block;
size = fixupAlignment(size);
if (size > m_left)
return nullptr;
m_block += size;
m_left -= size;
return m;
}
void dealloc(void* mem) override
{
CARB_UNUSED(mem);
// leak it - m_block will be freed when we're done with this allocator
}
private:
uint8_t* m_block = nullptr;
size_t m_left = 0;
};
static inline void clearJsonTree(JsonNode* node, Allocator* alloc)
{
switch (node->type)
{
case NodeType::eNull:
case NodeType::eBool:
case NodeType::eInt32:
case NodeType::eUint32:
case NodeType::eInt64:
case NodeType::eUint64:
case NodeType::eFloat32:
case NodeType::eFloat64:
break;
case NodeType::eString:
case NodeType::eBinary:
case NodeType::eBoolArray:
case NodeType::eInt32Array:
case NodeType::eUint32Array:
case NodeType::eInt64Array:
case NodeType::eUint64Array:
case NodeType::eFloat32Array:
case NodeType::eFloat64Array:
alloc->dealloc(node->data.strVal);
node->data.strVal = nullptr;
break;
case NodeType::eStringArray:
for (uint16_t i = 0; i < node->len; i++)
{
alloc->dealloc(node->data.strArrayVal[i]);
}
alloc->dealloc(node->data.strArrayVal);
node->data.strArrayVal = nullptr;
break;
case NodeType::eObjectArray:
// object arrays allocate their elements and their elements'
// properties in the same allocation
for (uint16_t i = 0; i < node->len; i++)
{
for (uint16_t j = 0; j < node->data.objVal[i].len; j++)
clearJsonTree(&node->data.objVal[i].data.objVal[j], alloc);
}
alloc->dealloc(node->data.objVal);
node->data.objVal = nullptr;
break;
case NodeType::eObject:
for (uint16_t i = 0; i < node->len; i++)
clearJsonTree(&node->data.objVal[i], alloc);
alloc->dealloc(node->data.objVal);
node->data.objVal = nullptr;
break;
}
alloc->dealloc(node->name);
node = {};
}
class TempJsonNode : public JsonNode
{
public:
TempJsonNode(Allocator* alloc)
{
m_alloc = alloc;
}
~TempJsonNode()
{
clearJsonTree(this, m_alloc);
}
private:
Allocator* m_alloc;
};
class JsonTreeSizeCalculator
{
public:
size_t getSize()
{
return m_count;
}
void trackRoot()
{
m_count += sizeof(JsonNode);
}
void trackObject(size_t propertyCount)
{
m_count += Allocator::fixupAlignment(sizeof(JsonNode) * propertyCount);
}
void trackObjectArray(size_t propertyCount, size_t len)
{
m_count += Allocator::fixupAlignment(sizeof(JsonNode) * (propertyCount + 1) * len);
}
void trackName(const char* name, uint16_t nameLen)
{
track(name, nameLen);
}
void trackName(const char* name)
{
track(name);
}
void track()
{
}
template <typename T>
void track(T value)
{
CARB_UNUSED(value);
static_assert(std::is_arithmetic<T>::value, "this is only valid for primitive types");
}
void track(const char* const* str, uint16_t len)
{
size_t size = 0;
if (len == 0)
return;
for (uint16_t i = 0; i < len; i++)
{
if (str[i] != nullptr)
size += Allocator::fixupAlignment(strlen(str[i]) + 1);
}
m_count += size + Allocator::fixupAlignment(sizeof(const char*) * len);
}
template <typename T>
void track(const T* value, uint16_t len)
{
CARB_UNUSED(value);
static_assert(std::is_arithmetic<T>::value, "this is only valid for primitive types");
m_count += Allocator::fixupAlignment(len * sizeof(T));
}
void track(const void* value, uint16_t len)
{
track(static_cast<const uint8_t*>(value), len);
}
void track(void* value, uint16_t len)
{
track(static_cast<const uint8_t*>(value), len);
}
void track(const char* str)
{
if (str != nullptr)
track(str, uint16_t(strlen(str) + 1));
}
void track(char* str)
{
track(static_cast<const char*>(str));
}
void track(const JsonNode* node)
{
trackName(node->name);
switch (node->type)
{
case NodeType::eNull:
track();
break;
case NodeType::eBool:
track(node->data.boolVal);
break;
case NodeType::eInt32:
case NodeType::eInt64:
track(node->data.intVal);
break;
case NodeType::eUint32:
case NodeType::eUint64:
track(node->data.uintVal);
break;
case NodeType::eFloat32:
case NodeType::eFloat64:
track(node->data.floatVal);
break;
case NodeType::eBinary:
track(node->data.binaryVal, node->len);
break;
case NodeType::eBoolArray:
track(node->data.boolArrayVal, node->len);
break;
case NodeType::eInt32Array:
track(node->data.uint32ArrayVal, node->len);
break;
case NodeType::eUint32Array:
track(node->data.int32ArrayVal, node->len);
break;
case NodeType::eInt64Array:
track(node->data.uint64ArrayVal, node->len);
break;
case NodeType::eUint64Array:
track(node->data.int64ArrayVal, node->len);
break;
case NodeType::eFloat32Array:
track(node->data.float32ArrayVal, node->len);
break;
case NodeType::eFloat64Array:
track(node->data.float64ArrayVal, node->len);
break;
case NodeType::eString:
track(node->data.strVal, node->len);
break;
case NodeType::eStringArray:
// if you don't cast this, it doesn't infer the template correctly
track(static_cast<const char* const*>(node->data.strArrayVal), node->len);
break;
case NodeType::eObjectArray:
if (node->len > 0)
trackObjectArray(node->data.objVal[0].len, node->len);
for (size_t i = 0; i < node->len; i++)
{
for (size_t j = 0; j < node->data.objVal[i].len; j++)
// if you don't cast this, it doesn't infer the template correctly
track(static_cast<const JsonNode*>(&node->data.objVal[i].data.objVal[j]));
}
break;
case NodeType::eObject:
trackObject(node->len);
for (size_t i = 0; i < node->len; i++)
// if you don't cast this, it doesn't infer the template correctly
track(static_cast<const JsonNode*>(&node->data.objVal[i]));
break;
}
}
void track(JsonNode* node)
{
return track(static_cast<const JsonNode*>(node));
}
private:
size_t m_count = 0;
};
class JsonBuilder
{
public:
JsonBuilder(Allocator* alloc)
{
m_alloc = alloc;
}
bool createObject(JsonNode* node, uint16_t propertyCount)
{
void* b;
CARB_ASSERT(node->type == NodeType::eNull);
CARB_ASSERT(node->len == 0);
b = m_alloc->alloc(propertyCount * sizeof(*node->data.objVal));
if (b == nullptr)
{
CARB_LOG_ERROR("allocator ran out of memory (node = '%s', requested %zu bytes)", node->name,
propertyCount * sizeof(*node->data.objVal));
return false;
}
node->data.objVal = new (b) JsonNode[propertyCount];
node->len = propertyCount;
node->type = NodeType::eObject;
CARB_ASSERT((uintptr_t(node->data.objVal) & (alignof(JsonNode) - 1)) == 0);
return true;
}
bool createObjectArray(JsonNode* node, uint16_t propertyCount, uint16_t len)
{
void* b;
CARB_ASSERT(node->type == NodeType::eNull);
CARB_ASSERT(node->len == 0);
// allocate it all as one array
b = m_alloc->alloc((len * (1 + propertyCount)) * sizeof(*node->data.objVal));
if (b == nullptr)
{
CARB_LOG_ERROR("allocator ran out of memory (node = '%s', requested %zu bytes)", node->name,
(len * (1 + propertyCount)) * sizeof(*node->data.objVal));
return false;
}
node->data.objVal = new (b) JsonNode[len];
CARB_ASSERT((uintptr_t(node->data.objVal) & (alignof(JsonNode) - 1)) == 0);
b = static_cast<uint8_t*>(b) + sizeof(JsonNode) * len;
for (size_t i = 0; i < len; i++)
{
node->data.objVal[i].data.objVal = new (b) JsonNode[propertyCount];
CARB_ASSERT((uintptr_t(node->data.objVal[i].data.objVal) & (alignof(JsonNode) - 1)) == 0);
b = static_cast<uint8_t*>(b) + sizeof(JsonNode) * propertyCount;
node->data.objVal[i].len = propertyCount;
node->data.objVal[i].type = NodeType::eObject;
}
node->len = len;
node->type = NodeType::eObjectArray;
return true;
}
bool setNode(JsonNode* node, bool b)
{
return setNode(node, b, &node->data.boolVal, NodeType::eBool);
}
bool setNode(JsonNode* node, const bool* data, uint16_t len)
{
return setNode(node, data, len, &node->data.boolArrayVal, NodeType::eBoolArray);
}
bool setNode(JsonNode* node, int32_t i)
{
return setNode(node, i, &node->data.intVal, NodeType::eInt32);
}
bool setNode(JsonNode* node, const int32_t* data, uint16_t len)
{
return setNode(node, data, len, &node->data.int32ArrayVal, NodeType::eInt32Array);
}
bool setNode(JsonNode* node, uint32_t u)
{
return setNode(node, u, &node->data.uintVal, NodeType::eUint32);
}
bool setNode(JsonNode* node, const uint32_t* data, uint16_t len)
{
return setNode(node, data, len, &node->data.uint32ArrayVal, NodeType::eUint32Array);
}
bool setNode(JsonNode* node, int64_t i)
{
return setNode(node, i, &node->data.intVal, NodeType::eInt64);
}
bool setNode(JsonNode* node, const int64_t* data, uint16_t len)
{
return setNode(node, data, len, &node->data.int64ArrayVal, NodeType::eInt64Array);
}
bool setNode(JsonNode* node, uint64_t u)
{
return setNode(node, u, &node->data.uintVal, NodeType::eUint64);
}
bool setNode(JsonNode* node, const uint64_t* data, uint16_t len)
{
return setNode(node, data, len, &node->data.uint64ArrayVal, NodeType::eUint64Array);
}
bool setNode(JsonNode* node, float f)
{
return setNode(node, f, &node->data.floatVal, NodeType::eFloat32);
}
bool setNode(JsonNode* node, const float* data, uint16_t len)
{
return setNode(node, data, len, &node->data.float32ArrayVal, NodeType::eFloat32Array);
}
bool setNode(JsonNode* node, double f)
{
return setNode(node, f, &node->data.floatVal, NodeType::eFloat64);
}
bool setNode(JsonNode* node, const double* data, uint16_t len)
{
return setNode(node, data, len, &node->data.float64ArrayVal, NodeType::eFloat64Array);
}
bool setNode(JsonNode* node, const char* const* data, uint16_t len)
{
CARB_ASSERT(node->type == NodeType::eNull);
CARB_ASSERT(node->len == 0);
if (len == 0)
{
node->data.strArrayVal = nullptr;
node->type = NodeType::eStringArray;
node->len = len;
return true;
}
// allocate each of the strings individually to avoid having to create
// a temporary buffer of string lengths or call strlen() multiple times
// on each string
node->data.strArrayVal = static_cast<char**>(m_alloc->alloc(len * sizeof(*node->data.strArrayVal)));
CARB_ASSERT((uintptr_t(node->data.strArrayVal) & (alignof(char*) - 1)) == 0);
if (node->data.strArrayVal == nullptr)
{
CARB_LOG_ERROR("allocator ran out of memory (node = '%s', requested %zu bytes)", node->name,
len * sizeof(*node->data.strArrayVal));
return false;
}
for (uint16_t i = 0; i < len; i++)
{
if (data[i] == nullptr)
{
node->data.strArrayVal[i] = nullptr;
}
else
{
size_t s = strlen(data[i]) + 1;
node->data.strArrayVal[i] = static_cast<char*>(m_alloc->alloc(s));
if (node->data.strArrayVal[i] == nullptr)
{
CARB_LOG_ERROR("allocator ran out of memory (requested %zu bytes)", s);
for (uint16_t j = 0; j < i; j++)
m_alloc->dealloc(node->data.strArrayVal[j]);
m_alloc->dealloc(node->data.strArrayVal);
node->data.strArrayVal = nullptr;
return false;
}
memcpy(node->data.strArrayVal[i], data[i], s);
}
}
node->type = NodeType::eStringArray;
node->len = len;
return true;
}
bool setNode(JsonNode* node, const char* str, uint16_t len)
{
return setNode(node, str, len, &node->data.strVal, NodeType::eString);
}
bool setNode(JsonNode* node, const char* str)
{
size_t len;
CARB_ASSERT(node->type == NodeType::eNull);
CARB_ASSERT(node->len == 0);
if (str == nullptr)
return setNode(node, str, 0);
len = strlen(str) + 1;
if (len > UINT16_MAX)
{
CARB_LOG_ERROR("string length exceeds 64KiB maximum (node = '%s', %zu characters, str = '%64s...')",
node->name, len, str);
return false;
}
return setNode(node, str, uint16_t(len));
}
bool setNode(JsonNode* node, const void* blob, uint16_t len)
{
return setNode<uint8_t>(node, static_cast<const uint8_t*>(blob), len, &node->data.binaryVal, NodeType::eBinary);
}
bool setNode(JsonNode* node, const uint8_t* blob, uint16_t len)
{
return setNode<uint8_t>(node, blob, len, &node->data.binaryVal, NodeType::eBinary);
}
bool setName(JsonNode* node, const char* name, uint16_t nameLen)
{
m_alloc->dealloc(node->name);
node->name = nullptr;
if (nameLen == 0)
return true;
node->name = static_cast<char*>(m_alloc->alloc(nameLen));
if (node->name == nullptr)
{
CARB_LOG_ERROR("allocator ran out of memory (name = '%s', requested %" PRIu16 " bytes)", name, nameLen);
return false;
}
node->nameLen = nameLen;
memcpy(node->name, name, nameLen);
return true;
}
bool setName(JsonNode* node, const char* name)
{
if (name == nullptr)
return setName(node, nullptr, 0);
return setName(node, name, uint16_t(strlen(name) + 1));
}
bool deepCopy(const JsonNode* node, JsonNode* out)
{
bool result = true;
CARB_ASSERT(out->type == NodeType::eNull);
CARB_ASSERT(out->len == 0);
if (!setName(out, node->name))
return false;
out->flags = node->flags;
switch (node->type)
{
case NodeType::eNull:
break;
case NodeType::eBool:
result = setNode(out, node->data.boolVal);
break;
case NodeType::eBoolArray:
result = setNode(out, node->data.boolArrayVal, node->len);
break;
case NodeType::eInt32:
result = setNode(out, int32_t(node->data.intVal));
break;
case NodeType::eInt64:
result = setNode(out, node->data.intVal);
break;
case NodeType::eInt32Array:
result = setNode(out, node->data.int32ArrayVal, node->len);
break;
case NodeType::eUint32:
result = setNode(out, uint32_t(node->data.uintVal));
break;
case NodeType::eUint64:
result = setNode(out, node->data.uintVal);
break;
case NodeType::eUint32Array:
result = setNode(out, node->data.uint32ArrayVal, node->len);
break;
case NodeType::eInt64Array:
result = setNode(out, node->data.int64ArrayVal, node->len);
break;
case NodeType::eUint64Array:
result = setNode(out, node->data.uint64ArrayVal, node->len);
break;
case NodeType::eFloat32:
result = setNode(out, float(node->data.floatVal));
break;
case NodeType::eFloat64:
result = setNode(out, node->data.floatVal);
break;
case NodeType::eFloat32Array:
result = setNode(out, node->data.float32ArrayVal, node->len);
break;
case NodeType::eFloat64Array:
result = setNode(out, node->data.float64ArrayVal, node->len);
break;
case NodeType::eString:
result = setNode(out, node->data.strVal, node->len);
break;
case NodeType::eStringArray:
result = setNode(out, node->data.strArrayVal, node->len);
break;
case NodeType::eBinary:
result = setNode(out, node->data.binaryVal, node->len);
break;
case NodeType::eObjectArray:
if (node->len == 0)
break;
result = createObjectArray(out, node->data.objVal[0].len, node->len);
if (!result)
break;
for (uint16_t i = 0; i < node->len; i++)
{
out->data.objVal[i].flags = node->data.objVal[i].flags;
for (uint16_t j = 0; result && j < node->data.objVal[i].len; j++)
{
result = deepCopy(&node->data.objVal[i].data.objVal[j], &out->data.objVal[i].data.objVal[j]);
}
}
break;
case NodeType::eObject:
result = createObject(out, node->len);
if (!result)
break;
for (size_t i = 0; i < node->len; i++)
{
deepCopy(&node->data.objVal[i], &out->data.objVal[i]);
}
break;
}
if (!result)
clearJsonTree(out, m_alloc);
return result;
}
template <bool failOnNonFatal = false>
static bool setFlags(JsonNode* node, JsonNode::Flag flags)
{
constexpr JsonNode::Flag fixedLengthConst = JsonNode::fFlagConst | JsonNode::fFlagFixedLength;
constexpr JsonNode::Flag constEnum = JsonNode::fFlagEnum | JsonNode::fFlagConst;
constexpr JsonNode::Flag validFlags = JsonNode::fFlagConst | JsonNode::fFlagFixedLength | JsonNode::fFlagEnum;
bool result = true;
CARB_ASSERT(node != nullptr);
// filter out unknown flags because they pose an ABI risk
if ((flags & ~validFlags) != 0)
{
CARB_LOG_ERROR("unknown flags were used %02x (node = '%s')", flags & ~validFlags, node->name);
return false;
}
// a node cannot be fixed length and constant
if ((flags & ~fixedLengthConst) == fixedLengthConst)
{
CARB_LOG_ERROR("attempted to set node to be both const and fixed length (node = '%s')", node->name);
result = !failOnNonFatal;
}
if ((flags & constEnum) == constEnum)
{
CARB_LOG_ERROR("a node cannot be both constant and an enum (node = '%s')", node->name);
flags &= JsonNode::Flag(~JsonNode::fFlagConst);
result = !failOnNonFatal;
}
if ((flags & JsonNode::fFlagEnum) != 0 && node->len == 0)
{
CARB_LOG_ERROR("an empty array can not be made into an enum (node = '%s')", node->name);
return false;
}
// check for invalid enum type usage
switch (node->type)
{
case NodeType::eNull:
case NodeType::eBool:
case NodeType::eInt32:
case NodeType::eUint32:
case NodeType::eInt64:
case NodeType::eUint64:
case NodeType::eFloat64:
case NodeType::eFloat32:
case NodeType::eObject:
case NodeType::eObjectArray:
case NodeType::eBinary:
case NodeType::eString:
if ((flags & JsonNode::fFlagEnum) != 0)
{
CARB_LOG_ERROR("an enum type must be on a non-object array type (node = '%s')", node->name);
return false;
}
break;
case NodeType::eBoolArray:
case NodeType::eInt32Array:
case NodeType::eUint32Array:
case NodeType::eInt64Array:
case NodeType::eUint64Array:
case NodeType::eFloat64Array:
case NodeType::eFloat32Array:
case NodeType::eStringArray:
// arrays can be const or fixed length
break;
}
// check for invalid const or fixed length usage
switch (node->type)
{
case NodeType::eNull:
case NodeType::eBool:
case NodeType::eInt32:
case NodeType::eUint32:
case NodeType::eInt64:
case NodeType::eUint64:
case NodeType::eFloat64:
case NodeType::eFloat32:
// fixed length is only valid on arrays
if ((flags & JsonNode::fFlagFixedLength) != 0)
{
CARB_LOG_ERROR("fixed length cannot be set on a scalar node (node = '%s')", node->name);
result = !failOnNonFatal;
}
break;
case NodeType::eObject:
if ((flags & JsonNode::fFlagConst) != 0)
{
CARB_LOG_ERROR("const is meaningless on an object node (node = '%s')", node->name);
result = !failOnNonFatal;
}
if ((flags & JsonNode::fFlagFixedLength) != 0)
{
CARB_LOG_ERROR("const is meaningless on an object node (node = '%s')", node->name);
result = !failOnNonFatal;
}
break;
case NodeType::eObjectArray:
if ((flags & JsonNode::fFlagConst) != 0)
{
CARB_LOG_ERROR("const is meaningless on an object array (node = '%s')", node->name);
result = !failOnNonFatal;
}
break;
case NodeType::eBinary:
case NodeType::eString:
case NodeType::eBoolArray:
case NodeType::eInt32Array:
case NodeType::eUint32Array:
case NodeType::eInt64Array:
case NodeType::eUint64Array:
case NodeType::eFloat64Array:
case NodeType::eFloat32Array:
case NodeType::eStringArray:
// arrays can be const or fixed length
break;
}
node->flags = flags;
return result;
}
private:
template <typename T0, typename T1>
bool setNode(JsonNode* node, T0 value, T1* dest, NodeType type)
{
CARB_ASSERT(node->type == NodeType::eNull);
CARB_ASSERT(node->len == 0);
node->len = 1;
node->type = type;
*dest = value;
return true;
}
template <typename T>
bool setNode(JsonNode* node, const T* data, uint16_t len, T** dest, NodeType type)
{
CARB_ASSERT(node->type == NodeType::eNull);
CARB_ASSERT(node->len == 0);
if (len == 0)
{
*dest = nullptr;
node->type = type;
node->len = len;
return true;
}
*dest = static_cast<T*>(m_alloc->alloc(len * sizeof(**dest)));
if (*dest == nullptr)
{
CARB_LOG_ERROR("allocator ran out of memory (requested %zu bytes)", len * sizeof(**dest));
return false;
}
CARB_ASSERT((uintptr_t(*dest) & (alignof(bool) - 1)) == 0);
node->type = type;
node->len = len;
memcpy(*dest, data, len * sizeof(*data));
return true;
}
Allocator* m_alloc;
};
} // namespace structuredlog
} // namespace omni