From 75d5ced3684d5b918b98807277f6756cedebd72e Mon Sep 17 00:00:00 2001 From: Thomas Arcila <134677+tarcila@users.noreply.github.com> Date: Wed, 27 May 2026 21:47:31 +0000 Subject: [PATCH 1/2] tsd/scripting: Split Lua bindings TUs to parallelise compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `tsd_lua` build wall dropped 74s → 17s by splitting sol2-heavy translation units that previously serialised the whole build: - ObjectBindings.cpp (was 72s): nine ObjectPoolRef registrations moved to bindings/refs/{Type}Ref.cpp; method bodies pulled into non-template free functions in ObjectMethodImpls.cpp; array helpers extracted to ArrayHelpers.cpp. - CoreBindings.cpp (was 31s): Object usertype moved to ObjectUsertype.cpp; Animation/AnimationManager moved to AnimationBindings.cpp; Scene's ~46 methods split across five bindings/scene/{Accessors,Creators,Iteration,Layers,Nodes}.cpp. - MathBindings.cpp (was 13s): five usertypes split into bindings/math/{Float2,Float3,Float4,Mat3,Mat4}.cpp with a shared RegisterVec.hpp for vec arithmetic. --- tsd/src/tsd/scripting/CMakeLists.txt | 23 + .../scripting/bindings/AnimationBindings.cpp | 247 ++++++ .../scripting/bindings/AnimationBindings.hpp | 12 + .../tsd/scripting/bindings/ArrayHelpers.cpp | 724 ++++++++++++++++ .../tsd/scripting/bindings/ArrayHelpers.hpp | 2 + .../tsd/scripting/bindings/CoreBindings.cpp | 566 +----------- .../tsd/scripting/bindings/MathBindings.cpp | 153 +--- .../tsd/scripting/bindings/MathUsertype.hpp | 20 + .../tsd/scripting/bindings/ObjectBindings.cpp | 820 +----------------- .../bindings/ObjectMethodBindings.hpp | 142 +-- .../scripting/bindings/ObjectMethodImpls.cpp | 187 ++++ .../scripting/bindings/ObjectMethodImpls.hpp | 84 ++ .../bindings/ObjectPoolRefRegister.hpp | 43 + .../scripting/bindings/ObjectRefBindings.hpp | 26 + .../tsd/scripting/bindings/ObjectUsertype.cpp | 27 + .../tsd/scripting/bindings/ObjectUsertype.hpp | 12 + .../tsd/scripting/bindings/SceneUsertype.hpp | 27 + .../tsd/scripting/bindings/math/Float2.cpp | 28 + .../tsd/scripting/bindings/math/Float3.cpp | 30 + .../tsd/scripting/bindings/math/Float4.cpp | 33 + tsd/src/tsd/scripting/bindings/math/Mat3.cpp | 47 + tsd/src/tsd/scripting/bindings/math/Mat4.cpp | 58 ++ .../scripting/bindings/math/RegisterVec.hpp | 46 + .../tsd/scripting/bindings/refs/ArrayRef.cpp | 48 + .../tsd/scripting/bindings/refs/CameraRef.cpp | 18 + .../scripting/bindings/refs/GeometryRef.cpp | 18 + .../tsd/scripting/bindings/refs/LightRef.cpp | 18 + .../scripting/bindings/refs/MaterialRef.cpp | 18 + .../scripting/bindings/refs/SamplerRef.cpp | 18 + .../bindings/refs/SpatialFieldRef.cpp | 26 + .../scripting/bindings/refs/SurfaceRef.cpp | 32 + .../tsd/scripting/bindings/refs/VolumeRef.cpp | 27 + .../scripting/bindings/scene/Accessors.cpp | 56 ++ .../tsd/scripting/bindings/scene/Creators.cpp | 160 ++++ .../scripting/bindings/scene/Iteration.cpp | 53 ++ .../tsd/scripting/bindings/scene/Layers.cpp | 69 ++ .../tsd/scripting/bindings/scene/Nodes.cpp | 79 ++ 37 files changed, 2405 insertions(+), 1592 deletions(-) create mode 100644 tsd/src/tsd/scripting/bindings/AnimationBindings.cpp create mode 100644 tsd/src/tsd/scripting/bindings/AnimationBindings.hpp create mode 100644 tsd/src/tsd/scripting/bindings/ArrayHelpers.cpp create mode 100644 tsd/src/tsd/scripting/bindings/MathUsertype.hpp create mode 100644 tsd/src/tsd/scripting/bindings/ObjectMethodImpls.cpp create mode 100644 tsd/src/tsd/scripting/bindings/ObjectMethodImpls.hpp create mode 100644 tsd/src/tsd/scripting/bindings/ObjectPoolRefRegister.hpp create mode 100644 tsd/src/tsd/scripting/bindings/ObjectRefBindings.hpp create mode 100644 tsd/src/tsd/scripting/bindings/ObjectUsertype.cpp create mode 100644 tsd/src/tsd/scripting/bindings/ObjectUsertype.hpp create mode 100644 tsd/src/tsd/scripting/bindings/SceneUsertype.hpp create mode 100644 tsd/src/tsd/scripting/bindings/math/Float2.cpp create mode 100644 tsd/src/tsd/scripting/bindings/math/Float3.cpp create mode 100644 tsd/src/tsd/scripting/bindings/math/Float4.cpp create mode 100644 tsd/src/tsd/scripting/bindings/math/Mat3.cpp create mode 100644 tsd/src/tsd/scripting/bindings/math/Mat4.cpp create mode 100644 tsd/src/tsd/scripting/bindings/math/RegisterVec.hpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/ArrayRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/CameraRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/GeometryRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/LightRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/MaterialRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/SamplerRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/SpatialFieldRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/SurfaceRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/refs/VolumeRef.cpp create mode 100644 tsd/src/tsd/scripting/bindings/scene/Accessors.cpp create mode 100644 tsd/src/tsd/scripting/bindings/scene/Creators.cpp create mode 100644 tsd/src/tsd/scripting/bindings/scene/Iteration.cpp create mode 100644 tsd/src/tsd/scripting/bindings/scene/Layers.cpp create mode 100644 tsd/src/tsd/scripting/bindings/scene/Nodes.cpp diff --git a/tsd/src/tsd/scripting/CMakeLists.txt b/tsd/src/tsd/scripting/CMakeLists.txt index bc1202dc7..12c1e6636 100644 --- a/tsd/src/tsd/scripting/CMakeLists.txt +++ b/tsd/src/tsd/scripting/CMakeLists.txt @@ -9,13 +9,36 @@ project_sources( PRIVATE LuaContext.cpp LuaBindings.cpp + bindings/ArrayHelpers.cpp + bindings/ObjectMethodImpls.cpp + bindings/ObjectUsertype.cpp + bindings/AnimationBindings.cpp bindings/CoreBindings.cpp + bindings/scene/Accessors.cpp + bindings/scene/Creators.cpp + bindings/scene/Iteration.cpp + bindings/scene/Layers.cpp + bindings/scene/Nodes.cpp bindings/MathBindings.cpp + bindings/math/Float2.cpp + bindings/math/Float3.cpp + bindings/math/Float4.cpp + bindings/math/Mat3.cpp + bindings/math/Mat4.cpp bindings/ObjectBindings.cpp bindings/LayerBindings.cpp bindings/IOBindings.cpp bindings/RenderBindings.cpp bindings/ParameterHelpers.cpp + bindings/refs/ArrayRef.cpp + bindings/refs/CameraRef.cpp + bindings/refs/GeometryRef.cpp + bindings/refs/LightRef.cpp + bindings/refs/MaterialRef.cpp + bindings/refs/SamplerRef.cpp + bindings/refs/SpatialFieldRef.cpp + bindings/refs/SurfaceRef.cpp + bindings/refs/VolumeRef.cpp ) project_include_directories( diff --git a/tsd/src/tsd/scripting/bindings/AnimationBindings.cpp b/tsd/src/tsd/scripting/bindings/AnimationBindings.cpp new file mode 100644 index 000000000..a42697c37 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/AnimationBindings.cpp @@ -0,0 +1,247 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +// Animation and AnimationManager sol2 usertypes. Lives in its own TU +// because each `new_usertype<>()` call is a heavy sol2 instantiation, +// and CoreBindings.cpp would otherwise be the build's critical path. + +#include "AnimationBindings.hpp" +#include "ArrayHelpers.hpp" +#include "tsd/animation/Animation.hpp" +#include "tsd/animation/AnimationManager.hpp" +#include "tsd/core/Token.hpp" +#include "tsd/scene/Object.hpp" +#include "tsd/scripting/LuaBindings.hpp" + +#include + +#include +#include +#include + +namespace tsd::scripting { + +namespace { + +std::vector tableToFloats(sol::table t) +{ + std::vector v(t.size()); + for (size_t i = 0; i < v.size(); i++) + v[i] = t[i + 1].get(); + return v; +} + +template +std::vector tableToVecs(sol::table t, const char *typeName) +{ + std::vector v(t.size()); + for (size_t i = 0; i < v.size(); i++) { + sol::object o = t[i + 1]; + if (o.is()) { + v[i] = o.as(); + } else if (o.is()) { + sol::table sub = o.as(); + if (sub.size() != N) + throw std::runtime_error( + std::string("expected ") + typeName + " or table of " + + std::to_string(N) + " numbers"); + if constexpr (N == 2) + v[i] = Vec(sub[1].get(), sub[2].get()); + else if constexpr (N == 3) + v[i] = Vec( + sub[1].get(), sub[2].get(), sub[3].get()); + else if constexpr (N == 4) + v[i] = Vec(sub[1].get(), + sub[2].get(), + sub[3].get(), + sub[4].get()); + } else { + throw std::runtime_error( + std::string("expected ") + typeName + " or table of " + + std::to_string(N) + " numbers"); + } + } + return v; +} + +} // namespace + +void registerAnimationBindings(sol::table &tsd) +{ + tsd.new_usertype( + "Animation", + sol::no_constructor, + "name", + &tsd::animation::Animation::name, + "addObjectParameterBinding", + [](tsd::animation::Animation &a, + sol::object target, + const std::string ¶m, + const std::string &typeStr, + sol::table dataTable, + sol::table timeBaseTable, + sol::optional interpStr) { + auto *obj = extractObjectPtr(target); + if (!obj) + throw std::runtime_error( + "addObjectParameterBinding: invalid target object"); + + auto interp = tsd::animation::InterpolationRule::LINEAR; + if (interpStr && *interpStr == "step") + interp = tsd::animation::InterpolationRule::STEP; + else if (interpStr && *interpStr == "slerp") + interp = tsd::animation::InterpolationRule::SLERP; + + auto dataType = arrayTypeFromString(typeStr); + auto tb = tableToFloats(timeBaseTable); + size_t count = std::min(dataTable.size(), tb.size()); + + if (anari::isObject(dataType)) { + std::vector ptrs(count); + for (size_t i = 0; i < count; i++) { + ptrs[i] = extractObjectPtr(dataTable[i + 1]); + if (!ptrs[i]) + throw std::runtime_error( + "addObjectParameterBinding: invalid object at index " + + std::to_string(i)); + } + a.addObjectParameterBinding( + obj, core::Token(param), dataType, ptrs.data(), tb.data(), + count, interp); + } else { + // Value types — decode into a typed vector, then pass as void* + auto addValues = [&](auto *typed, auto vec) { + (void)typed; + a.addObjectParameterBinding( + obj, core::Token(param), dataType, vec.data(), tb.data(), + count, interp); + }; + + switch (dataType) { + case ANARI_FLOAT32: + addValues((float *)nullptr, tableToFloats(dataTable)); + break; + case ANARI_FLOAT32_VEC2: + addValues((math::float2 *)nullptr, + tableToVecs(dataTable, "float2")); + break; + case ANARI_FLOAT32_VEC3: + addValues((math::float3 *)nullptr, + tableToVecs(dataTable, "float3")); + break; + case ANARI_FLOAT32_VEC4: + addValues((math::float4 *)nullptr, + tableToVecs(dataTable, "float4")); + break; + case ANARI_FLOAT32_MAT4: { + std::vector v(count); + for (size_t i = 0; i < count; i++) { + sol::object o = dataTable[i + 1]; + if (o.is()) + v[i] = o.as(); + else + throw std::runtime_error( + "addObjectParameterBinding: expected mat4 at index " + + std::to_string(i)); + } + a.addObjectParameterBinding( + obj, core::Token(param), dataType, v.data(), tb.data(), + count, interp); + break; + } + case ANARI_INT32: { + std::vector v(count); + for (size_t i = 0; i < count; i++) + v[i] = dataTable[i + 1].get(); + a.addObjectParameterBinding( + obj, core::Token(param), dataType, v.data(), tb.data(), + count, interp); + break; + } + case ANARI_UINT32: { + std::vector v(count); + for (size_t i = 0; i < count; i++) + v[i] = dataTable[i + 1].get(); + a.addObjectParameterBinding( + obj, core::Token(param), dataType, v.data(), tb.data(), + count, interp); + break; + } + default: + throw std::runtime_error( + "addObjectParameterBinding: unsupported data type '" + + typeStr + "'"); + } + } + }, + "addTransformBinding", + [](tsd::animation::Animation &a, + scene::LayerNodeRef node, + sol::table timeBaseTable, + sol::table rotTable, + sol::table transTable, + sol::table scaleTable) { + if (!node.valid()) + throw std::runtime_error("addTransformBinding: node must be valid"); + auto tb = tableToFloats(timeBaseTable); + auto rot = tableToVecs(rotTable, "float4"); + auto trans = tableToVecs(transTable, "float3"); + auto scale = tableToVecs(scaleTable, "float3"); + a.addTransformBinding( + node, tb.data(), rot.data(), trans.data(), scale.data(), tb.size()); + }); +} + +void registerAnimationManagerBindings(sol::state &lua) +{ + using SA = tsd::animation::AnimationManager; + sol::table tsd = lua["tsd"]; + + tsd.new_usertype( + "AnimationManager", + sol::no_constructor, + "addAnimation", + sol::overload( + [](SA &sa) -> tsd::animation::Animation & { + return sa.addAnimation(); + }, + [](SA &sa, const std::string &name) -> tsd::animation::Animation & { + return sa.addAnimation(name); + }), + "animations", + [](SA &sa) -> std::vector & { + return sa.animations(); + }, + "numberOfAnimations", + [](SA &sa) -> size_t { return sa.animations().size(); }, + "removeAnimation", + &SA::removeAnimation, + "removeAllAnimations", + &SA::removeAllAnimations, + "setAnimationTime", + &SA::setAnimationTime, + "getAnimationTime", + &SA::getAnimationTime, + "setAnimationIncrement", + &SA::setAnimationIncrement, + "getAnimationIncrement", + &SA::getAnimationIncrement, + "incrementAnimationTime", + &SA::incrementAnimationTime, + "getAnimationTotalFrames", + &SA::getAnimationTotalFrames, + "setAnimationTotalFrames", + &SA::setAnimationTotalFrames, + "getAnimationFPS", + &SA::getAnimationFPS, + "setAnimationFPS", + &SA::setAnimationFPS, + "getAnimationFrame", + &SA::getAnimationFrame, + "setAnimationFrame", + &SA::setAnimationFrame, + "incrementAnimationFrame", + &SA::incrementAnimationFrame); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/AnimationBindings.hpp b/tsd/src/tsd/scripting/bindings/AnimationBindings.hpp new file mode 100644 index 000000000..c067d6b16 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/AnimationBindings.hpp @@ -0,0 +1,12 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace tsd::scripting { + +void registerAnimationBindings(sol::table &tsd); + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ArrayHelpers.cpp b/tsd/src/tsd/scripting/bindings/ArrayHelpers.cpp new file mode 100644 index 000000000..8273278dc --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/ArrayHelpers.cpp @@ -0,0 +1,724 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "ArrayHelpers.hpp" +#include "tsd/core/Logging.hpp" +#include "tsd/core/Token.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scene/objects/Camera.hpp" +#include "tsd/scene/objects/Geometry.hpp" +#include "tsd/scene/objects/Light.hpp" +#include "tsd/scene/objects/Material.hpp" +#include "tsd/scene/objects/Sampler.hpp" +#include "tsd/scene/objects/SpatialField.hpp" +#include "tsd/scene/objects/Surface.hpp" +#include "tsd/scene/objects/Volume.hpp" + +#include +#include +#include +#include + +namespace tsd::scripting { + +template +class ScopedArrayMap +{ + public: + ScopedArrayMap(scene::Array &arr) : m_array(arr), m_ptr(arr.mapAs()) {} + ~ScopedArrayMap() + { + if (m_ptr) + m_array.unmap(); + } + + ScopedArrayMap(const ScopedArrayMap &) = delete; + ScopedArrayMap &operator=(const ScopedArrayMap &) = delete; + + T *data() + { + return m_ptr; + } + T &operator[](size_t i) + { + return m_ptr[i]; + } + + private: + scene::Array &m_array; + T *m_ptr; +}; + +scene::Object *extractObjectPtr(sol::object luaObj) +{ + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + auto ref = luaObj.as(); + return ref.valid() ? ref.data() : nullptr; + } + if (luaObj.is()) { + return luaObj.as(); + } + return nullptr; +} + +void arraySetObjectsFromLua(scene::Array &arr, sol::table data) +{ + const size_t count = data.size(); + const size_t copyCount = std::min(count, arr.size()); + + if (count != arr.size()) { + core::logWarning( + "Array.setData(): table size (%zu) differs from array size (%zu)%s", + count, + arr.size(), + count > arr.size() ? "; truncating" : "; padding with null objects"); + } + + ScopedArrayMap map(arr); + for (size_t i = 0; i < arr.size(); i++) + map[i] = size_t(-1); + + for (size_t i = 1; i <= copyCount; i++) { + auto *ptr = extractObjectPtr(data[i]); + if (!ptr) + throw std::runtime_error( + "createArray: invalid object at index " + std::to_string(i)); + map[i - 1] = ptr->index(); + } +} + +static void arraySetFromLua( + scene::Array &arr, sol::table data, sol::this_state s) +{ + if (anari::isObject(arr.elementType())) + arraySetObjectsFromLua(arr, data); + else + arraySetDataFromLua(arr, data, s); +} + +anari::DataType arrayTypeFromString(const std::string &typeStr) +{ + if (typeStr == "float") + return ANARI_FLOAT32; + if (typeStr == "float2") + return ANARI_FLOAT32_VEC2; + if (typeStr == "float3") + return ANARI_FLOAT32_VEC3; + if (typeStr == "float4") + return ANARI_FLOAT32_VEC4; + if (typeStr == "int") + return ANARI_INT32; + if (typeStr == "int2") + return ANARI_INT32_VEC2; + if (typeStr == "int3") + return ANARI_INT32_VEC3; + if (typeStr == "int4") + return ANARI_INT32_VEC4; + if (typeStr == "uint") + return ANARI_UINT32; + if (typeStr == "uint2") + return ANARI_UINT32_VEC2; + if (typeStr == "uint3") + return ANARI_UINT32_VEC3; + if (typeStr == "uint4") + return ANARI_UINT32_VEC4; + if (typeStr == "mat4") + return ANARI_FLOAT32_MAT4; + + // ANARI object types + if (typeStr == "spatialField") + return ANARI_SPATIAL_FIELD; + if (typeStr == "geometry") + return ANARI_GEOMETRY; + if (typeStr == "material") + return ANARI_MATERIAL; + if (typeStr == "surface") + return ANARI_SURFACE; + if (typeStr == "volume") + return ANARI_VOLUME; + if (typeStr == "light") + return ANARI_LIGHT; + if (typeStr == "camera") + return ANARI_CAMERA; + if (typeStr == "sampler") + return ANARI_SAMPLER; + if (typeStr == "array1d") + return ANARI_ARRAY1D; + + throw std::runtime_error(fmt::format( + "Unknown array type: '{}'. Valid types: float, float2, float3, float4, " + "int, int2, int3, int4, uint, uint2, uint3, uint4, mat4, " + "spatialField, geometry, material, surface, volume, light, camera, " + "sampler, array1d", + typeStr)); +} + +static size_t elementArity(anari::DataType elemType) +{ + switch (elemType) { + case ANARI_FLOAT32_VEC2: + case ANARI_INT32_VEC2: + case ANARI_UINT32_VEC2: + return 2; + case ANARI_FLOAT32_VEC3: + case ANARI_INT32_VEC3: + case ANARI_UINT32_VEC3: + return 3; + case ANARI_FLOAT32_VEC4: + case ANARI_INT32_VEC4: + case ANARI_UINT32_VEC4: + return 4; + default: + return 1; + } +} + +static bool isElementTable(sol::table t, anari::DataType elemType) +{ + return t.size() == elementArity(elemType); +} + +static bool isVecUserdata(sol::object o, anari::DataType elemType) +{ + switch (elemType) { + case ANARI_FLOAT32_VEC2: + return o.is(); + case ANARI_FLOAT32_VEC3: + return o.is(); + case ANARI_FLOAT32_VEC4: + return o.is(); + case ANARI_INT32_VEC2: + return o.is(); + case ANARI_INT32_VEC3: + return o.is(); + case ANARI_INT32_VEC4: + return o.is(); + case ANARI_UINT32_VEC2: + return o.is(); + case ANARI_UINT32_VEC3: + return o.is(); + case ANARI_UINT32_VEC4: + return o.is(); + default: + return false; + } +} + +static bool isElementLike(sol::object o, anari::DataType elemType) +{ + if (elemType == ANARI_FLOAT32_MAT4) + return o.is(); + const size_t arity = elementArity(elemType); + if (arity >= 2) { + return isVecUserdata(o, elemType) + || (o.is() && isElementTable(o.as(), elemType)); + } + // Scalar types + return o.is() || o.is() || o.is(); +} + +template +static Vec decodeVec(sol::object o, const char *typeName) +{ + if (o.is()) + return o.as(); + if (o.is()) { + sol::table t = o.as(); + if (t.size() != N) + throw std::runtime_error( + fmt::format("expected {} or table of {} numbers", typeName, N)); + if constexpr (N == 2) + return Vec(t[1].get(), t[2].get()); + else if constexpr (N == 3) + return Vec(t[1].get(), t[2].get(), t[3].get()); + else if constexpr (N == 4) + return Vec(t[1].get(), + t[2].get(), + t[3].get(), + t[4].get()); + } + throw std::runtime_error( + fmt::format("expected {} or table of {} numbers", typeName, N)); +} + +template +static void fillArrayFromFlattened(ScopedArrayMap &map, + size_t copyCount, + std::vector &flattened, + const char *typeName) +{ + for (size_t i = 0; i < copyCount; i++) + map[i] = decodeVec(flattened[i], typeName); +} + +template +static void fillArrayFromTable(ScopedArrayMap &map, + size_t copyCount, + sol::table &data, + const char *typeName) +{ + for (size_t i = 0; i < copyCount; i++) + map[i] = decodeVec(data[i + 1], typeName); +} + +template +static void getVecArrayAsLua(sol::state_view &lua, + ScopedArrayMap &map, + size_t count, + sol::table &out) +{ + for (size_t i = 0; i < count; i++) { + const auto &v = map[i]; + auto t = lua.create_table(static_cast(N), 0); + if constexpr (N >= 1) + t[1] = v.x; + if constexpr (N >= 2) + t[2] = v.y; + if constexpr (N >= 3) + t[3] = v.z; + if constexpr (N >= 4) + t[4] = v.w; + out[i + 1] = t; + } +} + +void inferArrayDimsFromLuaData(sol::table data, + anari::DataType elemType, + size_t &items0, + size_t &items1, + size_t &items2) +{ + items0 = 0; + items1 = 0; + items2 = 0; + + const size_t n = data.size(); + if (n == 0) + throw std::runtime_error( + "setParameterArray: cannot infer dimensions from empty data table"); + + // 1D candidate + bool is1D = true; + for (size_t i = 0; i < n; i++) { + if (!isElementLike(data[i + 1], elemType)) { + is1D = false; + break; + } + } + if (is1D) { + items0 = n; + return; + } + + // 2D candidate: data[y][x] + bool is2D = true; + size_t width = 0; + for (size_t y = 0; y < n && is2D; y++) { + sol::object rowObj = data[y + 1]; + if (!rowObj.is()) { + is2D = false; + break; + } + sol::table row = rowObj.as(); + if (y == 0) + width = row.size(); + if (row.size() != width || width == 0) { + is2D = false; + break; + } + for (size_t x = 0; x < width; x++) { + if (!isElementLike(row[x + 1], elemType)) { + is2D = false; + break; + } + } + } + if (is2D) { + items0 = width; + items1 = n; + return; + } + + // 3D candidate: data[z][y][x] + bool is3D = true; + size_t rows = 0; + width = 0; + for (size_t z = 0; z < n && is3D; z++) { + sol::object planeObj = data[z + 1]; + if (!planeObj.is()) { + is3D = false; + break; + } + sol::table plane = planeObj.as(); + if (z == 0) + rows = plane.size(); + if (plane.size() != rows || rows == 0) { + is3D = false; + break; + } + for (size_t y = 0; y < rows && is3D; y++) { + sol::object rowObj = plane[y + 1]; + if (!rowObj.is()) { + is3D = false; + break; + } + sol::table row = rowObj.as(); + if (z == 0 && y == 0) + width = row.size(); + if (row.size() != width || width == 0) { + is3D = false; + break; + } + for (size_t x = 0; x < width; x++) { + if (!isElementLike(row[x + 1], elemType)) { + is3D = false; + break; + } + } + } + } + if (is3D) { + items0 = width; + items1 = rows; + items2 = n; + return; + } + + throw std::runtime_error( + "setParameterArray: failed to infer dimensions from data shape; " + "provide explicit dimensions"); +} + +void arraySetDataFromLua(scene::Array &arr, sol::table data, sol::this_state s) +{ + const size_t dim0 = arr.dim(0); + const size_t dim1 = arr.dim(1); + const size_t dim2 = arr.dim(2); + + std::vector flattened; + + // 2D nested form: data[y][x], with size [dim1][dim0] + const bool canBeNested2D = dim2 == 1 && dim1 > 1 && data.size() == dim1; + if (canBeNested2D) { + bool valid = true; + for (size_t y = 0; y < dim1 && valid; y++) { + sol::object rowObj = data[y + 1]; + if (!rowObj.is()) { + valid = false; + break; + } + sol::table row = rowObj.as(); + if (row.size() != dim0) { + valid = false; + break; + } + } + if (valid) { + flattened.reserve(arr.size()); + for (size_t y = 0; y < dim1; y++) { + sol::table row = data[y + 1]; + for (size_t x = 0; x < dim0; x++) + flattened.push_back(row[x + 1]); + } + } + } + + // 3D nested form: data[z][y][x], with size [dim2][dim1][dim0] + if (flattened.empty() && dim2 > 1 && data.size() == dim2) { + bool valid = true; + for (size_t z = 0; z < dim2 && valid; z++) { + sol::object planeObj = data[z + 1]; + if (!planeObj.is()) { + valid = false; + break; + } + sol::table plane = planeObj.as(); + if (plane.size() != dim1) { + valid = false; + break; + } + for (size_t y = 0; y < dim1; y++) { + sol::object rowObj = plane[y + 1]; + if (!rowObj.is()) { + valid = false; + break; + } + sol::table row = rowObj.as(); + if (row.size() != dim0) { + valid = false; + break; + } + } + } + if (valid) { + flattened.reserve(arr.size()); + for (size_t z = 0; z < dim2; z++) { + sol::table plane = data[z + 1]; + for (size_t y = 0; y < dim1; y++) { + sol::table row = plane[y + 1]; + for (size_t x = 0; x < dim0; x++) + flattened.push_back(row[x + 1]); + } + } + } + } + + const bool usingNestedForm = !flattened.empty(); + const size_t count = usingNestedForm ? flattened.size() : data.size(); + if (count == 0) + return; + + if (!usingNestedForm && count != arr.size()) { + sol::state_view lua(s); + std::string msg = fmt::format( + "Array.setData: table size ({}) differs from array size ({})", + count, + arr.size()); + if (count > arr.size()) { + msg += "; truncating"; + } else { + msg += "; remaining elements unchanged"; + } + lua["print"](msg); + } + + auto elemType = arr.elementType(); + size_t copyCount = std::min(count, arr.size()); + +#define FILL_VEC(ANARI_TYPE, Vec, Elem, N, name) \ + if (elemType == ANARI_TYPE) { \ + ScopedArrayMap map(arr); \ + if (usingNestedForm) \ + fillArrayFromFlattened(map, copyCount, flattened, name); \ + else \ + fillArrayFromTable(map, copyCount, data, name); \ + } + + try { + if (elemType == ANARI_FLOAT32) { + ScopedArrayMap map(arr); + if (usingNestedForm) { + for (size_t i = 0; i < copyCount; i++) + map[i] = flattened[i].as(); + } else { + for (size_t i = 0; i < copyCount; i++) + map[i] = data[i + 1].get(); + } + } else if (elemType == ANARI_INT32) { + ScopedArrayMap map(arr); + if (usingNestedForm) { + for (size_t i = 0; i < copyCount; i++) + map[i] = flattened[i].as(); + } else { + for (size_t i = 0; i < copyCount; i++) + map[i] = data[i + 1].get(); + } + } else if (elemType == ANARI_UINT32) { + ScopedArrayMap map(arr); + if (usingNestedForm) { + for (size_t i = 0; i < copyCount; i++) + map[i] = flattened[i].as(); + } else { + for (size_t i = 0; i < copyCount; i++) + map[i] = data[i + 1].get(); + } + } else + FILL_VEC(ANARI_FLOAT32_VEC2, math::float2, float, 2, "float2") + else FILL_VEC(ANARI_FLOAT32_VEC3, + math::float3, + float, + 3, + "float3") else FILL_VEC(ANARI_FLOAT32_VEC4, + math::float4, + float, + 4, + "float4") else FILL_VEC(ANARI_INT32_VEC2, + math::int2, + int32_t, + 2, + "int2") else FILL_VEC(ANARI_INT32_VEC3, + math::int3, + int32_t, + 3, + "int3") else FILL_VEC(ANARI_INT32_VEC4, + math::int4, + int32_t, + 4, + "int4") else FILL_VEC(ANARI_UINT32_VEC2, + math::uint2, + uint32_t, + 2, + "uint2") else FILL_VEC(ANARI_UINT32_VEC3, + math::uint3, + uint32_t, + 3, + "uint3") else FILL_VEC(ANARI_UINT32_VEC4, + math::uint4, + uint32_t, + 4, + "uint4") else if (elemType == ANARI_FLOAT32_MAT4) + { + ScopedArrayMap map(arr); + if (usingNestedForm) { + for (size_t i = 0; i < copyCount; i++) + map[i] = flattened[i].as(); + } else { + for (size_t i = 0; i < copyCount; i++) + map[i] = data[i + 1].get(); + } + } + else + { + throw std::runtime_error("Array.setData: unsupported element type"); + } + } catch (const sol::error &e) { + throw std::runtime_error("Array.setData: failed to convert table element - " + + std::string(e.what())); + } + +#undef FILL_VEC +} + +scene::ArrayRef setParameterArrayFromLua(scene::Object &obj, + const std::string &name, + const std::string &typeStr, + sol::table data, + sol::this_state s) +{ + auto *scene = obj.scene(); + if (!scene) + throw std::runtime_error( + "setParameterArray: object is not attached to a scene"); + + const auto elemType = arrayTypeFromString(typeStr); + size_t items0 = 0, items1 = 0, items2 = 0; + if (anari::isObject(elemType)) + items0 = data.size(); + else + inferArrayDimsFromLuaData(data, elemType, items0, items1, items2); + + auto arr = scene->createArray(elemType, items0, items1, items2); + if (!arr.valid()) + throw std::runtime_error("setParameterArray: failed to create array"); + + arraySetFromLua(*arr.data(), data, s); + obj.setParameterObject(core::Token(name), *arr.data()); + return arr; +} + +scene::ArrayRef setParameterArrayFromLua(scene::Object &obj, + const std::string &name, + const std::string &typeStr, + size_t items0, + size_t items1, + size_t items2, + sol::table data, + sol::this_state s) +{ + auto *scene = obj.scene(); + if (!scene) + throw std::runtime_error( + "setParameterArray: object is not attached to a scene"); + + const auto elemType = arrayTypeFromString(typeStr); + auto arr = scene->createArray(elemType, items0, items1, items2); + if (!arr.valid()) + throw std::runtime_error("setParameterArray: failed to create array"); + + arraySetFromLua(*arr.data(), data, s); + obj.setParameterObject(core::Token(name), *arr.data()); + return arr; +} + +sol::table arrayGetDataAsLua(scene::Array &arr, sol::this_state s) +{ + sol::state_view lua(s); + const auto count = arr.size(); + auto out = lua.create_table(static_cast(count), 0); + + if (count == 0) + return out; + + const auto elemType = arr.elementType(); + +#define GET_VEC(ANARI_TYPE, Vec, N) \ + if (elemType == ANARI_TYPE) { \ + ScopedArrayMap map(arr); \ + getVecArrayAsLua(lua, map, count, out); \ + } + + if (elemType == ANARI_FLOAT32) { + ScopedArrayMap map(arr); + for (size_t i = 0; i < count; i++) + out[i + 1] = map[i]; + } else if (elemType == ANARI_INT32) { + ScopedArrayMap map(arr); + for (size_t i = 0; i < count; i++) + out[i + 1] = map[i]; + } else if (elemType == ANARI_UINT32) { + ScopedArrayMap map(arr); + for (size_t i = 0; i < count; i++) + out[i + 1] = map[i]; + } else + GET_VEC(ANARI_FLOAT32_VEC2, math::float2, 2) + else GET_VEC(ANARI_FLOAT32_VEC3, math::float3, 3) else GET_VEC( + ANARI_FLOAT32_VEC4, math::float4, 4) else GET_VEC(ANARI_INT32_VEC2, + math::int2, + 2) else GET_VEC(ANARI_INT32_VEC3, + math::int3, + 3) else GET_VEC(ANARI_INT32_VEC4, + math::int4, + 4) else GET_VEC(ANARI_UINT32_VEC2, + math::uint2, + 2) else GET_VEC(ANARI_UINT32_VEC3, + math::uint3, + 3) else GET_VEC(ANARI_UINT32_VEC4, math::uint4, 4) else if (elemType + == ANARI_FLOAT32_MAT4) + { + ScopedArrayMap map(arr); + for (size_t i = 0; i < count; i++) + out[i + 1] = map[i]; + } + else + { + throw std::runtime_error("Array.getData: unsupported element type"); + } + +#undef GET_VEC + + return out; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ArrayHelpers.hpp b/tsd/src/tsd/scripting/bindings/ArrayHelpers.hpp index abec4fe25..875a516f5 100644 --- a/tsd/src/tsd/scripting/bindings/ArrayHelpers.hpp +++ b/tsd/src/tsd/scripting/bindings/ArrayHelpers.hpp @@ -39,4 +39,6 @@ scene::ArrayRef setParameterArrayFromLua(scene::Object &obj, sol::table data, sol::this_state s); +sol::table arrayGetDataAsLua(scene::Array &arr, sol::this_state s); + } // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/CoreBindings.cpp b/tsd/src/tsd/scripting/bindings/CoreBindings.cpp index 40e61ffe5..25388665e 100644 --- a/tsd/src/tsd/scripting/bindings/CoreBindings.cpp +++ b/tsd/src/tsd/scripting/bindings/CoreBindings.cpp @@ -1,135 +1,20 @@ // Copyright 2026 NVIDIA Corporation // SPDX-License-Identifier: Apache-2.0 -#include "ArrayHelpers.hpp" -#include "ObjectMethodBindings.hpp" -#include "ParameterHelpers.hpp" -#include "tsd/animation/Animation.hpp" -#include "tsd/animation/AnimationManager.hpp" +#include "AnimationBindings.hpp" +#include "ObjectUsertype.hpp" +#include "SceneUsertype.hpp" #include "tsd/core/Token.hpp" -#include "tsd/scene/Object.hpp" #include "tsd/scene/Parameter.hpp" #include "tsd/scene/Scene.hpp" -#include "tsd/scene/objects/Array.hpp" -#include "tsd/scene/objects/Sampler.hpp" #include "tsd/scripting/LuaBindings.hpp" #include "tsd/scripting/Sol2Helpers.hpp" #include -namespace tsd::scripting { - -// Animation helpers /////////////////////////////////////////////////////////// - -static std::vector tableToFloats(sol::table t) -{ - std::vector v(t.size()); - for (size_t i = 0; i < v.size(); i++) - v[i] = t[i + 1].get(); - return v; -} - -template -static std::vector tableToVecs(sol::table t, const char *typeName) -{ - std::vector v(t.size()); - for (size_t i = 0; i < v.size(); i++) { - sol::object o = t[i + 1]; - if (o.is()) { - v[i] = o.as(); - } else if (o.is()) { - sol::table sub = o.as(); - if (sub.size() != N) - throw std::runtime_error( - std::string("expected ") + typeName + " or table of " - + std::to_string(N) + " numbers"); - if constexpr (N == 2) - v[i] = Vec(sub[1].get(), sub[2].get()); - else if constexpr (N == 3) - v[i] = Vec( - sub[1].get(), sub[2].get(), sub[3].get()); - else if constexpr (N == 4) - v[i] = Vec(sub[1].get(), - sub[2].get(), - sub[3].get(), - sub[4].get()); - } else { - throw std::runtime_error( - std::string("expected ") + typeName + " or table of " - + std::to_string(N) + " numbers"); - } - } - return v; -} - -static scene::ArrayRef createArrayFromLua(scene::Scene &scene, - const std::string &typeStr, - size_t items0, - size_t items1, - size_t items2) -{ - return scene.createArray( - arrayTypeFromString(typeStr), items0, items1, items2); -} - -static scene::ArrayRef createArrayFromLua(scene::Scene &scene, - const std::string &typeStr, - size_t items0, - size_t items1, - size_t items2, - sol::table data, - sol::this_state s) -{ - const auto elemType = arrayTypeFromString(typeStr); - const bool isObj = anari::isObject(elemType); - - if (items0 == 0) { - if (isObj) { - items0 = data.size(); - } else { - inferArrayDimsFromLuaData(data, elemType, items0, items1, items2); - } - } - - auto arr = scene.createArray(elemType, items0, items1, items2); - if (!arr.valid()) - throw std::runtime_error("createArray: failed to create array"); - - if (isObj) - arraySetObjectsFromLua(*arr.data(), data); - else - arraySetDataFromLua(*arr.data(), data, s); - - return arr; -} +#include -template -static auto makeForEach(F poolAccessor) -{ - return [poolAccessor](scene::Scene &s, sol::function fn) { - const auto &pool = poolAccessor(s.objectDB()); - for (size_t i = 0; i < pool.capacity(); i++) { - if (!pool.slot_empty(i)) { - sol::object result = fn(pool.at(i)); - if (result.is() && !result.as()) - break; - } - } - }; -} - -template -static auto makeCreateBinding() -{ - return [](scene::Scene &s, - const std::string &subtype, - sol::optional params) { - auto ref = s.createObject(core::Token(subtype)); - if (params) - applyParameterTable(ref.data(), *params); - return ref; - }; -} +namespace tsd::scripting { void registerContextBindings(sol::state &lua) { @@ -157,384 +42,23 @@ void registerContextBindings(sol::state &lua) "isEnabled", &scene::Parameter::isEnabled); - auto objectType = tsd.new_usertype( - "Object", sol::no_constructor, "index", &scene::Object::index); - - registerObjectMethodsOn( - objectType, [](scene::Object &o) -> scene::Object * { return &o; }); - - tsd.new_usertype( - "Scene", - sol::constructors(), - // Object creation - "createGeometry", - makeCreateBinding(), - "createMaterial", - makeCreateBinding(), - "createLight", - makeCreateBinding(), - "createCamera", - makeCreateBinding(), - "createSampler", - makeCreateBinding(), - "createVolume", - makeCreateBinding(), - "createSpatialField", - makeCreateBinding(), - "createSurface", - [](scene::Scene &s, - const std::string &name, - scene::GeometryRef g, - scene::MaterialRef m, - sol::optional params) { - auto ref = s.createSurface(name.c_str(), g, m); - if (params) - applyParameterTable(ref.data(), *params); - return ref; - }, - "createArray", - sol::overload( - // (typeStr, table) — infer dims from data - [](scene::Scene &s, - const std::string &typeStr, - sol::table data, - sol::this_state st) { - return createArrayFromLua(s, typeStr, 0, 0, 0, data, st); - }, - // (typeStr, items0) — empty 1D - [](scene::Scene &s, const std::string &typeStr, size_t items0) { - return createArrayFromLua(s, typeStr, items0, 0, 0); - }, - // (typeStr, items0, table) — 1D with data - [](scene::Scene &s, - const std::string &typeStr, - size_t items0, - sol::table data, - sol::this_state st) { - return createArrayFromLua(s, typeStr, items0, 0, 0, data, st); - }, - // (typeStr, items0, items1) — empty 2D - [](scene::Scene &s, - const std::string &typeStr, - size_t items0, - size_t items1) { - return createArrayFromLua(s, typeStr, items0, items1, 0); - }, - // (typeStr, items0, items1, table) — 2D with data - [](scene::Scene &s, - const std::string &typeStr, - size_t items0, - size_t items1, - sol::table data, - sol::this_state st) { - return createArrayFromLua(s, typeStr, items0, items1, 0, data, st); - }, - // (typeStr, items0, items1, items2) — empty 3D - [](scene::Scene &s, - const std::string &typeStr, - size_t items0, - size_t items1, - size_t items2) { - return createArrayFromLua(s, typeStr, items0, items1, items2); - }, - // (typeStr, items0, items1, items2, table) — 3D with data - [](scene::Scene &s, - const std::string &typeStr, - size_t items0, - size_t items1, - size_t items2, - sol::table data, - sol::this_state st) { - return createArrayFromLua( - s, typeStr, items0, items1, items2, data, st); - }), - // Object access - "getGeometry", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getMaterial", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getLight", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getCamera", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getSurface", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getArray", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getVolume", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getSampler", - [](scene::Scene &s, size_t i) { return s.getObject(i); }, - "getSpatialField", - [](scene::Scene &s, size_t i) { - return s.getObject(i); - }, - // Object counts - "numberOfObjects", - [](scene::Scene &s, anari::DataType type) -> size_t { - return s.numberOfObjects(type); - }, - // Iteration over objects - "forEachGeometry", - makeForEach([](auto &db) -> auto & { return db.geometry; }), - "forEachMaterial", - makeForEach([](auto &db) -> auto & { return db.material; }), - "forEachSurface", - makeForEach([](auto &db) -> auto & { return db.surface; }), - "forEachLight", - makeForEach([](auto &db) -> auto & { return db.light; }), - "forEachCamera", - makeForEach([](auto &db) -> auto & { return db.camera; }), - "forEachVolume", - makeForEach([](auto &db) -> auto & { return db.volume; }), - "forEachSpatialField", - makeForEach([](auto &db) -> auto & { return db.field; }), - "forEachSampler", - makeForEach([](auto &db) -> auto & { return db.sampler; }), - "forEachArray", - makeForEach([](auto &db) -> auto & { return db.array; }), - // Layers - "addLayer", - [](scene::Scene &s, const std::string &name) { - return s.addLayer(core::Token(name)); - }, - "layer", - sol::overload( - [](scene::Scene &s, const std::string &name) { - return s.layer(core::Token(name)); - }, - [](scene::Scene &s, size_t i) { return s.layer(i); }), - "numberOfLayers", - &scene::Scene::numberOfLayers, - "defaultLayer", - &scene::Scene::defaultLayer, - "defaultMaterial", - &scene::Scene::defaultMaterial, - // Node insertion - "insertChildNode", - [](scene::Scene &s, scene::LayerNodeRef parent, const std::string &name) { - return s.insertChildNode(parent, name.c_str()); - }, - "insertChildTransformNode", - [](scene::Scene &s, - scene::LayerNodeRef parent, - const math::mat4 &xfm, - const std::string &name) { - return s.insertChildTransformNode(parent, xfm, name.c_str()); - }, - "insertChildTransformArrayNode", - sol::overload( - [](scene::Scene &s, - scene::LayerNodeRef parent, - scene::Array &a, - const std::string &name) { - return s.insertChildTransformArrayNode(parent, &a, name.c_str()); - }, - [](scene::Scene &s, - scene::LayerNodeRef parent, - scene::ArrayRef a, - const std::string &name) { - if (!a) - throw std::runtime_error( - "insertChildTransformArrayNode: invalid array"); - return s.insertChildTransformArrayNode( - parent, a.data(), name.c_str()); - }), - // Object node insertion (adds objects to the renderable scene graph) - "insertObjectNode", - [](scene::Scene &s, - scene::LayerNodeRef parent, - sol::object objArg, - sol::optional name) { - auto *obj = extractObjectPtr(objArg); - if (!obj) - throw std::runtime_error("insertObjectNode: invalid object argument"); - return s.insertChildObjectNode( - parent, obj->type(), obj->index(), name.value_or("").c_str()); - }, - // Object removal - "removeObject", - [](scene::Scene &s, sol::object objArg) { - auto *obj = extractObjectPtr(objArg); - if (obj) - s.removeObject(obj); - }, - "removeAllObjects", - &scene::Scene::removeAllObjects, - // Layer removal - "removeLayer", - sol::overload( - [](scene::Scene &s, const std::string &name) { - s.removeLayer(core::Token(name)); - }, - [](scene::Scene &s, scene::Layer *layer) { s.removeLayer(layer); }), - "removeAllLayers", - &scene::Scene::removeAllLayers, - // Layer active state - "layerIsActive", - [](scene::Scene &s, const std::string &name) { - return s.layerIsActive(core::Token(name)); - }, - "setLayerActive", - [](scene::Scene &s, const std::string &name, bool active) { - s.setLayerActive(core::Token(name), active); - }, - "setAllLayersActive", - &scene::Scene::setAllLayersActive, - "setOnlyLayerActive", - [](scene::Scene &s, const std::string &name) { - s.setOnlyLayerActive(core::Token(name)); - }, - "numberOfActiveLayers", - &scene::Scene::numberOfActiveLayers, - // Signal layer changes - "signalLayerStructureChanged", - [](scene::Scene &s, scene::Layer *l) { - if (l) - s.signalLayerStructureChanged(l); - }, - "signalLayerTransformChanged", - [](scene::Scene &s, scene::Layer *l) { - if (l) - s.signalLayerTransformChanged(l); - }, - // Node removal - "removeNode", - sol::overload( - [](scene::Scene &s, scene::LayerNodeRef obj) { s.removeNode(obj); }, - [](scene::Scene &s, scene::LayerNodeRef obj, bool deleteObjects) { - s.removeNode(obj, deleteObjects); - }), - // Cleanup - "removeUnusedObjects", - &scene::Scene::removeUnusedObjects, - "defragmentObjectStorage", - &scene::Scene::defragmentObjectStorage, - "cleanupScene", - &scene::Scene::cleanupScene); - - tsd.new_usertype( - "Animation", - sol::no_constructor, - "name", - &tsd::animation::Animation::name, - "addObjectParameterBinding", - [](tsd::animation::Animation &a, - sol::object target, - const std::string ¶m, - const std::string &typeStr, - sol::table dataTable, - sol::table timeBaseTable, - sol::optional interpStr) { - auto *obj = extractObjectPtr(target); - if (!obj) - throw std::runtime_error( - "addObjectParameterBinding: invalid target object"); - - auto interp = tsd::animation::InterpolationRule::LINEAR; - if (interpStr && *interpStr == "step") - interp = tsd::animation::InterpolationRule::STEP; - else if (interpStr && *interpStr == "slerp") - interp = tsd::animation::InterpolationRule::SLERP; + // scene::Object usertype lives in ObjectUsertype.cpp. + registerObjectUsertype(tsd); - auto dataType = arrayTypeFromString(typeStr); - auto tb = tableToFloats(timeBaseTable); - size_t count = std::min(dataTable.size(), tb.size()); + // scene::Scene usertype is created here with just its constructor; + // each method group is layered on by a per-group TU under + // bindings/scene/ so they parallelise across cores. + auto sceneType = tsd.new_usertype( + "Scene", sol::constructors()); - if (anari::isObject(dataType)) { - std::vector ptrs(count); - for (size_t i = 0; i < count; i++) { - ptrs[i] = extractObjectPtr(dataTable[i + 1]); - if (!ptrs[i]) - throw std::runtime_error( - "addObjectParameterBinding: invalid object at index " - + std::to_string(i)); - } - a.addObjectParameterBinding( - obj, core::Token(param), dataType, ptrs.data(), tb.data(), - count, interp); - } else { - // Value types — decode into a typed vector, then pass as void* - auto addValues = [&](auto *typed, auto vec) { - (void)typed; - a.addObjectParameterBinding( - obj, core::Token(param), dataType, vec.data(), tb.data(), - count, interp); - }; + registerSceneCreators(sceneType); + registerSceneAccessors(sceneType); + registerSceneIteration(sceneType); + registerSceneLayers(sceneType); + registerSceneNodes(sceneType); - switch (dataType) { - case ANARI_FLOAT32: - addValues((float *)nullptr, tableToFloats(dataTable)); - break; - case ANARI_FLOAT32_VEC2: - addValues((math::float2 *)nullptr, - tableToVecs(dataTable, "float2")); - break; - case ANARI_FLOAT32_VEC3: - addValues((math::float3 *)nullptr, - tableToVecs(dataTable, "float3")); - break; - case ANARI_FLOAT32_VEC4: - addValues((math::float4 *)nullptr, - tableToVecs(dataTable, "float4")); - break; - case ANARI_FLOAT32_MAT4: { - std::vector v(count); - for (size_t i = 0; i < count; i++) { - sol::object o = dataTable[i + 1]; - if (o.is()) - v[i] = o.as(); - else - throw std::runtime_error( - "addObjectParameterBinding: expected mat4 at index " - + std::to_string(i)); - } - a.addObjectParameterBinding( - obj, core::Token(param), dataType, v.data(), tb.data(), - count, interp); - break; - } - case ANARI_INT32: { - std::vector v(count); - for (size_t i = 0; i < count; i++) - v[i] = dataTable[i + 1].get(); - a.addObjectParameterBinding( - obj, core::Token(param), dataType, v.data(), tb.data(), - count, interp); - break; - } - case ANARI_UINT32: { - std::vector v(count); - for (size_t i = 0; i < count; i++) - v[i] = dataTable[i + 1].get(); - a.addObjectParameterBinding( - obj, core::Token(param), dataType, v.data(), tb.data(), - count, interp); - break; - } - default: - throw std::runtime_error( - "addObjectParameterBinding: unsupported data type '" - + typeStr + "'"); - } - } - }, - "addTransformBinding", - [](tsd::animation::Animation &a, - scene::LayerNodeRef node, - sol::table timeBaseTable, - sol::table rotTable, - sol::table transTable, - sol::table scaleTable) { - if (!node.valid()) - throw std::runtime_error("addTransformBinding: node must be valid"); - auto tb = tableToFloats(timeBaseTable); - auto rot = tableToVecs(rotTable, "float4"); - auto trans = tableToVecs(transTable, "float3"); - auto scale = tableToVecs(scaleTable, "float3"); - a.addTransformBinding( - node, tb.data(), rot.data(), trans.data(), scale.data(), tb.size()); - }); + // Animation usertype lives in AnimationBindings.cpp. + registerAnimationBindings(tsd); tsd["createScene"] = []() { return std::make_unique(); }; @@ -550,56 +74,4 @@ void registerContextBindings(sol::state &lua) tsd["SPATIAL_FIELD"] = ANARI_SPATIAL_FIELD; } -void registerAnimationManagerBindings(sol::state &lua) -{ - using SA = tsd::animation::AnimationManager; - sol::table tsd = lua["tsd"]; - - tsd.new_usertype( - "AnimationManager", - sol::no_constructor, - "addAnimation", - sol::overload( - [](SA &sa) -> tsd::animation::Animation & { - return sa.addAnimation(); - }, - [](SA &sa, const std::string &name) -> tsd::animation::Animation & { - return sa.addAnimation(name); - }), - "animations", - [](SA &sa) -> std::vector & { - return sa.animations(); - }, - "numberOfAnimations", - [](SA &sa) -> size_t { return sa.animations().size(); }, - "removeAnimation", - &SA::removeAnimation, - "removeAllAnimations", - &SA::removeAllAnimations, - "setAnimationTime", - &SA::setAnimationTime, - "getAnimationTime", - &SA::getAnimationTime, - "setAnimationIncrement", - &SA::setAnimationIncrement, - "getAnimationIncrement", - &SA::getAnimationIncrement, - "incrementAnimationTime", - &SA::incrementAnimationTime, - "getAnimationTotalFrames", - &SA::getAnimationTotalFrames, - "setAnimationTotalFrames", - &SA::setAnimationTotalFrames, - "getAnimationFPS", - &SA::getAnimationFPS, - "setAnimationFPS", - &SA::setAnimationFPS, - "getAnimationFrame", - &SA::getAnimationFrame, - "setAnimationFrame", - &SA::setAnimationFrame, - "incrementAnimationFrame", - &SA::incrementAnimationFrame); -} - } // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/MathBindings.cpp b/tsd/src/tsd/scripting/bindings/MathBindings.cpp index 9a96ca238..95905e225 100644 --- a/tsd/src/tsd/scripting/bindings/MathBindings.cpp +++ b/tsd/src/tsd/scripting/bindings/MathBindings.cpp @@ -1,161 +1,28 @@ // Copyright 2026 NVIDIA Corporation // SPDX-License-Identifier: Apache-2.0 +#include "MathUsertype.hpp" #include "tsd/core/TSDMath.hpp" #include "tsd/scripting/LuaBindings.hpp" -#include #include namespace tsd::scripting { -template -static void registerVecArithmetic(sol::usertype &ut) -{ - ut[sol::meta_function::addition] = [](const T &a, const T &b) { - return a + b; - }; - ut[sol::meta_function::subtraction] = [](const T &a, const T &b) { - return a - b; - }; - ut[sol::meta_function::multiplication] = - [](sol::object lhs, sol::object rhs) -> T { - if (lhs.is() && rhs.is()) - return lhs.as() * rhs.as(); - if (lhs.is() && rhs.is()) - return lhs.as() * static_cast(rhs.as()); - if (lhs.is() && rhs.is()) - return static_cast(lhs.as()) * rhs.as(); - throw std::runtime_error("invalid operand types for *"); - }; - ut[sol::meta_function::division] = - [](sol::object lhs, sol::object rhs) -> T { - if (lhs.is() && rhs.is()) - return lhs.as() / rhs.as(); - if (lhs.is() && rhs.is()) - return lhs.as() / static_cast(rhs.as()); - throw std::runtime_error("invalid operand types for /"); - }; - ut[sol::meta_function::unary_minus] = [](const T &a) { return -a; }; -} - void registerMathBindings(sol::state &lua) { sol::table tsd = lua["tsd"]; - auto float2Type = tsd.new_usertype("float2", - sol::constructors(), - "x", - &math::float2::x, - "y", - &math::float2::y, - sol::meta_function::to_string, - [](const math::float2 &v) { - return fmt::format("float2({}, {})", v.x, v.y); - }); - registerVecArithmetic(float2Type); + // Each math usertype lives in its own TU under bindings/math/ so the + // five sol2 instantiations parallelise across cores. + registerFloat2Usertype(tsd); + registerFloat3Usertype(tsd); + registerFloat4Usertype(tsd); + registerMat3Usertype(tsd); + registerMat4Usertype(tsd); - auto float3Type = tsd.new_usertype("float3", - sol::constructors(), - "x", - &math::float3::x, - "y", - &math::float3::y, - "z", - &math::float3::z, - sol::meta_function::to_string, - [](const math::float3 &v) { - return fmt::format("float3({}, {}, {})", v.x, v.y, v.z); - }); - registerVecArithmetic(float3Type); - - auto float4Type = tsd.new_usertype("float4", - sol::constructors(), - "x", - &math::float4::x, - "y", - &math::float4::y, - "z", - &math::float4::z, - "w", - &math::float4::w, - sol::meta_function::to_string, - [](const math::float4 &v) { - return fmt::format("float4({}, {}, {}, {})", v.x, v.y, v.z, v.w); - }); - registerVecArithmetic(float4Type); - - tsd.new_usertype( - "mat3", - sol::constructors(), - sol::meta_function::index, - [](sol::this_state L, const math::mat3 &m, sol::object key) -> sol::object { - if (key.is()) { - int i = key.as(); - if (i < 0 || i > 2) - throw std::out_of_range("mat3 index must be 0, 1, or 2"); - return sol::make_object(L, m[i]); - } - if (key.is() && key.as() == "identity") - return sol::make_object(L, math::IDENTITY_MAT3); - return sol::lua_nil; - }, - sol::meta_function::new_index, - [](math::mat3 &m, int i, const math::float3 &v) { - if (i < 0 || i > 2) - throw std::out_of_range("mat3 index must be 0, 1, or 2"); - m[i] = v; - }, - sol::meta_function::to_string, - [](const math::mat3 &m) { - return fmt::format("mat3({}, {}, {})", - fmt::format("float3({}, {}, {})", m[0].x, m[0].y, m[0].z), - fmt::format("float3({}, {}, {})", m[1].x, m[1].y, m[1].z), - fmt::format("float3({}, {}, {})", m[2].x, m[2].y, m[2].z)); - }); - - tsd.new_usertype("mat4", - sol::constructors(), - sol::meta_function::multiplication, - sol::overload([](const math::mat4 &a, - const math::mat4 &b) { return math::mul(a, b); }, - [](const math::mat4 &a, const math::float4 &v) { - return math::mul(a, v); - }), - sol::meta_function::index, - [](sol::this_state L, const math::mat4 &m, sol::object key) -> sol::object { - if (key.is()) { - int i = key.as(); - if (i < 0 || i > 3) - throw std::out_of_range("mat4 index must be 0, 1, 2, or 3"); - return sol::make_object(L, m[i]); - } - if (key.is() && key.as() == "identity") - return sol::make_object(L, math::IDENTITY_MAT4); - return sol::lua_nil; - }, - sol::meta_function::new_index, - [](math::mat4 &m, int i, const math::float4 &v) { - if (i < 0 || i > 3) - throw std::out_of_range("mat4 index must be 0, 1, 2, or 3"); - m[i] = v; - }, - sol::meta_function::to_string, - [](const math::mat4 &m) { - return fmt::format("mat4({}, {}, {}, {})", - fmt::format( - "float4({}, {}, {}, {})", m[0].x, m[0].y, m[0].z, m[0].w), - fmt::format( - "float4({}, {}, {}, {})", m[1].x, m[1].y, m[1].z, m[1].w), - fmt::format( - "float4({}, {}, {}, {})", m[2].x, m[2].y, m[2].z, m[2].w), - fmt::format( - "float4({}, {}, {}, {})", m[3].x, m[3].y, m[3].z, m[3].w)); - }); + // Top-level constructor functions and math utilities — these are + // cheap to compile, so they stay in this TU. tsd["float2"] = sol::overload([]() { return math::float2(); }, diff --git a/tsd/src/tsd/scripting/bindings/MathUsertype.hpp b/tsd/src/tsd/scripting/bindings/MathUsertype.hpp new file mode 100644 index 000000000..2d19ddd42 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/MathUsertype.hpp @@ -0,0 +1,20 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Per-type entry points for the math usertype registrations. Each lives +// in its own TU under bindings/math/ so the five sol2 instantiations +// (float2/3/4, mat3, mat4) compile in parallel. + +#include + +namespace tsd::scripting { + +void registerFloat2Usertype(sol::table &tsd); +void registerFloat3Usertype(sol::table &tsd); +void registerFloat4Usertype(sol::table &tsd); +void registerMat3Usertype(sol::table &tsd); +void registerMat4Usertype(sol::table &tsd); + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ObjectBindings.cpp b/tsd/src/tsd/scripting/bindings/ObjectBindings.cpp index ad105b287..c0608cf7c 100644 --- a/tsd/src/tsd/scripting/bindings/ObjectBindings.cpp +++ b/tsd/src/tsd/scripting/bindings/ObjectBindings.cpp @@ -2,10 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "ArrayHelpers.hpp" -#include "ObjectMethodBindings.hpp" -#include "ParameterHelpers.hpp" -#include "tsd/core/Logging.hpp" -#include "tsd/core/Token.hpp" +#include "ObjectRefBindings.hpp" #include "tsd/scene/Scene.hpp" #include "tsd/scene/objects/Array.hpp" #include "tsd/scene/objects/Camera.hpp" @@ -19,750 +16,19 @@ #include "tsd/scripting/LuaBindings.hpp" #include "tsd/scripting/Sol2Helpers.hpp" -#include -#include -#include #include -#include - -namespace tsd::scripting { - -template -class ScopedArrayMap -{ - public: - ScopedArrayMap(scene::Array &arr) : m_array(arr), m_ptr(arr.mapAs()) {} - ~ScopedArrayMap() - { - if (m_ptr) - m_array.unmap(); - } - - ScopedArrayMap(const ScopedArrayMap &) = delete; - ScopedArrayMap &operator=(const ScopedArrayMap &) = delete; - - T *data() - { - return m_ptr; - } - T &operator[](size_t i) - { - return m_ptr[i]; - } - - private: - scene::Array &m_array; - T *m_ptr; -}; - -scene::Object *extractObjectPtr(sol::object luaObj) -{ - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - auto ref = luaObj.as(); - return ref.valid() ? ref.data() : nullptr; - } - if (luaObj.is()) { - return luaObj.as(); - } - return nullptr; -} - -void arraySetObjectsFromLua(scene::Array &arr, sol::table data) -{ - const size_t count = data.size(); - const size_t copyCount = std::min(count, arr.size()); - - if (count != arr.size()) { - core::logWarning( - "Array.setData(): table size (%zu) differs from array size (%zu)%s", - count, - arr.size(), - count > arr.size() ? "; truncating" : "; padding with null objects"); - } - - ScopedArrayMap map(arr); - for (size_t i = 0; i < arr.size(); i++) - map[i] = size_t(-1); - - for (size_t i = 1; i <= copyCount; i++) { - auto *ptr = extractObjectPtr(data[i]); - if (!ptr) - throw std::runtime_error( - "createArray: invalid object at index " + std::to_string(i)); - map[i - 1] = ptr->index(); - } -} - -static void arraySetFromLua( - scene::Array &arr, sol::table data, sol::this_state s) -{ - if (anari::isObject(arr.elementType())) - arraySetObjectsFromLua(arr, data); - else - arraySetDataFromLua(arr, data, s); -} - -anari::DataType arrayTypeFromString(const std::string &typeStr) -{ - if (typeStr == "float") - return ANARI_FLOAT32; - if (typeStr == "float2") - return ANARI_FLOAT32_VEC2; - if (typeStr == "float3") - return ANARI_FLOAT32_VEC3; - if (typeStr == "float4") - return ANARI_FLOAT32_VEC4; - if (typeStr == "int") - return ANARI_INT32; - if (typeStr == "int2") - return ANARI_INT32_VEC2; - if (typeStr == "int3") - return ANARI_INT32_VEC3; - if (typeStr == "int4") - return ANARI_INT32_VEC4; - if (typeStr == "uint") - return ANARI_UINT32; - if (typeStr == "uint2") - return ANARI_UINT32_VEC2; - if (typeStr == "uint3") - return ANARI_UINT32_VEC3; - if (typeStr == "uint4") - return ANARI_UINT32_VEC4; - if (typeStr == "mat4") - return ANARI_FLOAT32_MAT4; - - // ANARI object types - if (typeStr == "spatialField") - return ANARI_SPATIAL_FIELD; - if (typeStr == "geometry") - return ANARI_GEOMETRY; - if (typeStr == "material") - return ANARI_MATERIAL; - if (typeStr == "surface") - return ANARI_SURFACE; - if (typeStr == "volume") - return ANARI_VOLUME; - if (typeStr == "light") - return ANARI_LIGHT; - if (typeStr == "camera") - return ANARI_CAMERA; - if (typeStr == "sampler") - return ANARI_SAMPLER; - if (typeStr == "array1d") - return ANARI_ARRAY1D; - - throw std::runtime_error(fmt::format( - "Unknown array type: '{}'. Valid types: float, float2, float3, float4, " - "int, int2, int3, int4, uint, uint2, uint3, uint4, mat4, " - "spatialField, geometry, material, surface, volume, light, camera, " - "sampler, array1d", - typeStr)); -} - -static size_t elementArity(anari::DataType elemType) -{ - switch (elemType) { - case ANARI_FLOAT32_VEC2: - case ANARI_INT32_VEC2: - case ANARI_UINT32_VEC2: - return 2; - case ANARI_FLOAT32_VEC3: - case ANARI_INT32_VEC3: - case ANARI_UINT32_VEC3: - return 3; - case ANARI_FLOAT32_VEC4: - case ANARI_INT32_VEC4: - case ANARI_UINT32_VEC4: - return 4; - default: - return 1; - } -} - -static bool isElementTable(sol::table t, anari::DataType elemType) -{ - return t.size() == elementArity(elemType); -} - -static bool isVecUserdata(sol::object o, anari::DataType elemType) -{ - switch (elemType) { - case ANARI_FLOAT32_VEC2: - return o.is(); - case ANARI_FLOAT32_VEC3: - return o.is(); - case ANARI_FLOAT32_VEC4: - return o.is(); - case ANARI_INT32_VEC2: - return o.is(); - case ANARI_INT32_VEC3: - return o.is(); - case ANARI_INT32_VEC4: - return o.is(); - case ANARI_UINT32_VEC2: - return o.is(); - case ANARI_UINT32_VEC3: - return o.is(); - case ANARI_UINT32_VEC4: - return o.is(); - default: - return false; - } -} - -static bool isElementLike(sol::object o, anari::DataType elemType) -{ - if (elemType == ANARI_FLOAT32_MAT4) - return o.is(); - const size_t arity = elementArity(elemType); - if (arity >= 2) { - return isVecUserdata(o, elemType) - || (o.is() && isElementTable(o.as(), elemType)); - } - // Scalar types - return o.is() || o.is() || o.is(); -} - -template -static Vec decodeVec(sol::object o, const char *typeName) -{ - if (o.is()) - return o.as(); - if (o.is()) { - sol::table t = o.as(); - if (t.size() != N) - throw std::runtime_error( - fmt::format("expected {} or table of {} numbers", typeName, N)); - if constexpr (N == 2) - return Vec(t[1].get(), t[2].get()); - else if constexpr (N == 3) - return Vec(t[1].get(), t[2].get(), t[3].get()); - else if constexpr (N == 4) - return Vec(t[1].get(), - t[2].get(), - t[3].get(), - t[4].get()); - } - throw std::runtime_error( - fmt::format("expected {} or table of {} numbers", typeName, N)); -} - -template -static void fillArrayFromFlattened(ScopedArrayMap &map, - size_t copyCount, - std::vector &flattened, - const char *typeName) -{ - for (size_t i = 0; i < copyCount; i++) - map[i] = decodeVec(flattened[i], typeName); -} - -template -static void fillArrayFromTable(ScopedArrayMap &map, - size_t copyCount, - sol::table &data, - const char *typeName) -{ - for (size_t i = 0; i < copyCount; i++) - map[i] = decodeVec(data[i + 1], typeName); -} - -template -static void getVecArrayAsLua(sol::state_view &lua, - ScopedArrayMap &map, - size_t count, - sol::table &out) -{ - for (size_t i = 0; i < count; i++) { - const auto &v = map[i]; - auto t = lua.create_table(static_cast(N), 0); - if constexpr (N >= 1) - t[1] = v.x; - if constexpr (N >= 2) - t[2] = v.y; - if constexpr (N >= 3) - t[3] = v.z; - if constexpr (N >= 4) - t[4] = v.w; - out[i + 1] = t; - } -} - -void inferArrayDimsFromLuaData(sol::table data, - anari::DataType elemType, - size_t &items0, - size_t &items1, - size_t &items2) -{ - items0 = 0; - items1 = 0; - items2 = 0; - - const size_t n = data.size(); - if (n == 0) - throw std::runtime_error( - "setParameterArray: cannot infer dimensions from empty data table"); - - // 1D candidate - bool is1D = true; - for (size_t i = 0; i < n; i++) { - if (!isElementLike(data[i + 1], elemType)) { - is1D = false; - break; - } - } - if (is1D) { - items0 = n; - return; - } - - // 2D candidate: data[y][x] - bool is2D = true; - size_t width = 0; - for (size_t y = 0; y < n && is2D; y++) { - sol::object rowObj = data[y + 1]; - if (!rowObj.is()) { - is2D = false; - break; - } - sol::table row = rowObj.as(); - if (y == 0) - width = row.size(); - if (row.size() != width || width == 0) { - is2D = false; - break; - } - for (size_t x = 0; x < width; x++) { - if (!isElementLike(row[x + 1], elemType)) { - is2D = false; - break; - } - } - } - if (is2D) { - items0 = width; - items1 = n; - return; - } - - // 3D candidate: data[z][y][x] - bool is3D = true; - size_t rows = 0; - width = 0; - for (size_t z = 0; z < n && is3D; z++) { - sol::object planeObj = data[z + 1]; - if (!planeObj.is()) { - is3D = false; - break; - } - sol::table plane = planeObj.as(); - if (z == 0) - rows = plane.size(); - if (plane.size() != rows || rows == 0) { - is3D = false; - break; - } - for (size_t y = 0; y < rows && is3D; y++) { - sol::object rowObj = plane[y + 1]; - if (!rowObj.is()) { - is3D = false; - break; - } - sol::table row = rowObj.as(); - if (z == 0 && y == 0) - width = row.size(); - if (row.size() != width || width == 0) { - is3D = false; - break; - } - for (size_t x = 0; x < width; x++) { - if (!isElementLike(row[x + 1], elemType)) { - is3D = false; - break; - } - } - } - } - if (is3D) { - items0 = width; - items1 = rows; - items2 = n; - return; - } - - throw std::runtime_error( - "setParameterArray: failed to infer dimensions from data shape; " - "provide explicit dimensions"); -} - -void arraySetDataFromLua(scene::Array &arr, sol::table data, sol::this_state s) -{ - const size_t dim0 = arr.dim(0); - const size_t dim1 = arr.dim(1); - const size_t dim2 = arr.dim(2); - - std::vector flattened; - - // 2D nested form: data[y][x], with size [dim1][dim0] - const bool canBeNested2D = dim2 == 1 && dim1 > 1 && data.size() == dim1; - if (canBeNested2D) { - bool valid = true; - for (size_t y = 0; y < dim1 && valid; y++) { - sol::object rowObj = data[y + 1]; - if (!rowObj.is()) { - valid = false; - break; - } - sol::table row = rowObj.as(); - if (row.size() != dim0) { - valid = false; - break; - } - } - if (valid) { - flattened.reserve(arr.size()); - for (size_t y = 0; y < dim1; y++) { - sol::table row = data[y + 1]; - for (size_t x = 0; x < dim0; x++) - flattened.push_back(row[x + 1]); - } - } - } - - // 3D nested form: data[z][y][x], with size [dim2][dim1][dim0] - if (flattened.empty() && dim2 > 1 && data.size() == dim2) { - bool valid = true; - for (size_t z = 0; z < dim2 && valid; z++) { - sol::object planeObj = data[z + 1]; - if (!planeObj.is()) { - valid = false; - break; - } - sol::table plane = planeObj.as(); - if (plane.size() != dim1) { - valid = false; - break; - } - for (size_t y = 0; y < dim1; y++) { - sol::object rowObj = plane[y + 1]; - if (!rowObj.is()) { - valid = false; - break; - } - sol::table row = rowObj.as(); - if (row.size() != dim0) { - valid = false; - break; - } - } - } - if (valid) { - flattened.reserve(arr.size()); - for (size_t z = 0; z < dim2; z++) { - sol::table plane = data[z + 1]; - for (size_t y = 0; y < dim1; y++) { - sol::table row = plane[y + 1]; - for (size_t x = 0; x < dim0; x++) - flattened.push_back(row[x + 1]); - } - } - } - } - - const bool usingNestedForm = !flattened.empty(); - const size_t count = usingNestedForm ? flattened.size() : data.size(); - if (count == 0) - return; - - if (!usingNestedForm && count != arr.size()) { - sol::state_view lua(s); - std::string msg = fmt::format( - "Array.setData: table size ({}) differs from array size ({})", - count, - arr.size()); - if (count > arr.size()) { - msg += "; truncating"; - } else { - msg += "; remaining elements unchanged"; - } - lua["print"](msg); - } - - auto elemType = arr.elementType(); - size_t copyCount = std::min(count, arr.size()); - -#define FILL_VEC(ANARI_TYPE, Vec, Elem, N, name) \ - if (elemType == ANARI_TYPE) { \ - ScopedArrayMap map(arr); \ - if (usingNestedForm) \ - fillArrayFromFlattened(map, copyCount, flattened, name); \ - else \ - fillArrayFromTable(map, copyCount, data, name); \ - } - - try { - if (elemType == ANARI_FLOAT32) { - ScopedArrayMap map(arr); - if (usingNestedForm) { - for (size_t i = 0; i < copyCount; i++) - map[i] = flattened[i].as(); - } else { - for (size_t i = 0; i < copyCount; i++) - map[i] = data[i + 1].get(); - } - } else if (elemType == ANARI_INT32) { - ScopedArrayMap map(arr); - if (usingNestedForm) { - for (size_t i = 0; i < copyCount; i++) - map[i] = flattened[i].as(); - } else { - for (size_t i = 0; i < copyCount; i++) - map[i] = data[i + 1].get(); - } - } else if (elemType == ANARI_UINT32) { - ScopedArrayMap map(arr); - if (usingNestedForm) { - for (size_t i = 0; i < copyCount; i++) - map[i] = flattened[i].as(); - } else { - for (size_t i = 0; i < copyCount; i++) - map[i] = data[i + 1].get(); - } - } else - FILL_VEC(ANARI_FLOAT32_VEC2, math::float2, float, 2, "float2") - else FILL_VEC(ANARI_FLOAT32_VEC3, - math::float3, - float, - 3, - "float3") else FILL_VEC(ANARI_FLOAT32_VEC4, - math::float4, - float, - 4, - "float4") else FILL_VEC(ANARI_INT32_VEC2, - math::int2, - int32_t, - 2, - "int2") else FILL_VEC(ANARI_INT32_VEC3, - math::int3, - int32_t, - 3, - "int3") else FILL_VEC(ANARI_INT32_VEC4, - math::int4, - int32_t, - 4, - "int4") else FILL_VEC(ANARI_UINT32_VEC2, - math::uint2, - uint32_t, - 2, - "uint2") else FILL_VEC(ANARI_UINT32_VEC3, - math::uint3, - uint32_t, - 3, - "uint3") else FILL_VEC(ANARI_UINT32_VEC4, - math::uint4, - uint32_t, - 4, - "uint4") else if (elemType == ANARI_FLOAT32_MAT4) - { - ScopedArrayMap map(arr); - if (usingNestedForm) { - for (size_t i = 0; i < copyCount; i++) - map[i] = flattened[i].as(); - } else { - for (size_t i = 0; i < copyCount; i++) - map[i] = data[i + 1].get(); - } - } - else - { - throw std::runtime_error("Array.setData: unsupported element type"); - } - } catch (const sol::error &e) { - throw std::runtime_error("Array.setData: failed to convert table element - " - + std::string(e.what())); - } - -#undef FILL_VEC -} - -scene::ArrayRef setParameterArrayFromLua(scene::Object &obj, - const std::string &name, - const std::string &typeStr, - sol::table data, - sol::this_state s) -{ - auto *scene = obj.scene(); - if (!scene) - throw std::runtime_error( - "setParameterArray: object is not attached to a scene"); - - const auto elemType = arrayTypeFromString(typeStr); - size_t items0 = 0, items1 = 0, items2 = 0; - if (anari::isObject(elemType)) - items0 = data.size(); - else - inferArrayDimsFromLuaData(data, elemType, items0, items1, items2); - - auto arr = scene->createArray(elemType, items0, items1, items2); - if (!arr.valid()) - throw std::runtime_error("setParameterArray: failed to create array"); - - arraySetFromLua(*arr.data(), data, s); - obj.setParameterObject(core::Token(name), *arr.data()); - return arr; -} -scene::ArrayRef setParameterArrayFromLua(scene::Object &obj, - const std::string &name, - const std::string &typeStr, - size_t items0, - size_t items1, - size_t items2, - sol::table data, - sol::this_state s) -{ - auto *scene = obj.scene(); - if (!scene) - throw std::runtime_error( - "setParameterArray: object is not attached to a scene"); - - const auto elemType = arrayTypeFromString(typeStr); - auto arr = scene->createArray(elemType, items0, items1, items2); - if (!arr.valid()) - throw std::runtime_error("setParameterArray: failed to create array"); - - arraySetFromLua(*arr.data(), data, s); - obj.setParameterObject(core::Token(name), *arr.data()); - return arr; -} - -static sol::table arrayGetDataAsLua(scene::Array &arr, sol::this_state s) -{ - sol::state_view lua(s); - const auto count = arr.size(); - auto out = lua.create_table(static_cast(count), 0); - - if (count == 0) - return out; - - const auto elemType = arr.elementType(); - -#define GET_VEC(ANARI_TYPE, Vec, N) \ - if (elemType == ANARI_TYPE) { \ - ScopedArrayMap map(arr); \ - getVecArrayAsLua(lua, map, count, out); \ - } - - if (elemType == ANARI_FLOAT32) { - ScopedArrayMap map(arr); - for (size_t i = 0; i < count; i++) - out[i + 1] = map[i]; - } else if (elemType == ANARI_INT32) { - ScopedArrayMap map(arr); - for (size_t i = 0; i < count; i++) - out[i + 1] = map[i]; - } else if (elemType == ANARI_UINT32) { - ScopedArrayMap map(arr); - for (size_t i = 0; i < count; i++) - out[i + 1] = map[i]; - } else - GET_VEC(ANARI_FLOAT32_VEC2, math::float2, 2) - else GET_VEC(ANARI_FLOAT32_VEC3, math::float3, 3) else GET_VEC( - ANARI_FLOAT32_VEC4, math::float4, 4) else GET_VEC(ANARI_INT32_VEC2, - math::int2, - 2) else GET_VEC(ANARI_INT32_VEC3, - math::int3, - 3) else GET_VEC(ANARI_INT32_VEC4, - math::int4, - 4) else GET_VEC(ANARI_UINT32_VEC2, - math::uint2, - 2) else GET_VEC(ANARI_UINT32_VEC3, - math::uint3, - 3) else GET_VEC(ANARI_UINT32_VEC4, math::uint4, 4) else if (elemType - == ANARI_FLOAT32_MAT4) - { - ScopedArrayMap map(arr); - for (size_t i = 0; i < count; i++) - out[i + 1] = map[i]; - } - else - { - throw std::runtime_error("Array.getData: unsupported element type"); - } - -#undef GET_VEC - - return out; -} - -// Register an ObjectPoolRef usertype with forwarded Object methods. -// -// All Object methods are available directly on refs: -// ref:setParameter("radius", 1.5) -// ref.name = "myGeom" -// ref:removeParameter("radius") -template -auto registerObjectPoolRef(sol::table &tsd, const char *name) -{ - using Ref = scene::ObjectPoolRef; - auto refType = tsd.new_usertype( - name, - sol::no_constructor, - "valid", - &Ref::valid, - "index", - [](const Ref &r) -> size_t { return r.index(); }, - sol::meta_function::to_string, - [name](const Ref &r) { - if (!r.valid()) - return fmt::format("{}(invalid)", name); - return fmt::format("{}({})", name, r.index()); - }); - - registerObjectMethodsOn(refType, - [](Ref &r) -> scene::Object * { return r.valid() ? r.data() : nullptr; }); +#include - return refType; -} +namespace tsd::scripting { void registerObjectBindings(sol::state &lua) { sol::table tsd = lua["tsd"]; - // Register concrete Object types first (metatables keyed by C++ type_index) - // Then register Ref types which overwrite tsd["Name"] table entries. + // Concrete Object types — metatables are keyed by C++ type_index; the + // per-type Ref registrations below overwrite the named `tsd["Name"]` + // entries so Lua code lands on the Ref usertype, not the Object one. tsd.new_usertype("Geometry", sol::no_constructor, @@ -864,68 +130,18 @@ void registerObjectBindings(sol::state &lua) return arrayGetDataAsLua(arr, s); }); - registerObjectPoolRef(tsd, "Geometry"); - registerObjectPoolRef(tsd, "Material"); - registerObjectPoolRef(tsd, "Light"); - registerObjectPoolRef(tsd, "Camera"); - registerObjectPoolRef(tsd, "Sampler"); - - auto surfaceRefType = registerObjectPoolRef(tsd, "Surface"); - surfaceRefType["geometry"] = +[](scene::SurfaceRef &r) -> scene::Geometry * { - if (!r.valid()) - return nullptr; - return r.data()->parameterValueAsObject("geometry"); - }; - surfaceRefType["material"] = +[](scene::SurfaceRef &r) -> scene::Material * { - if (!r.valid()) - return nullptr; - return r.data()->parameterValueAsObject("material"); - }; - - auto volumeRefType = registerObjectPoolRef(tsd, "Volume"); - volumeRefType["spatialField"] = - +[](scene::VolumeRef &r) -> scene::SpatialField * { - if (!r.valid()) - return nullptr; - return r.data()->parameterValueAsObject("value"); - }; - - auto fieldRefType = - registerObjectPoolRef(tsd, "SpatialField"); - fieldRefType["computeValueRange"] = - +[](scene::SpatialFieldRef &r) -> math::float2 { - if (!r.valid()) - return math::float2(0.f); - return r.data()->computeValueRange(); - }; - - auto arrayRefType = registerObjectPoolRef(tsd, "Array"); - arrayRefType["elementType"] = +[](const scene::ArrayRef &r) -> anari::DataType { - return r.valid() ? r.data()->elementType() : ANARI_UNKNOWN; - }; - arrayRefType["size"] = +[](const scene::ArrayRef &r) -> size_t { - return r.valid() ? r.data()->size() : 0; - }; - arrayRefType["elementSize"] = +[](const scene::ArrayRef &r) -> size_t { - return r.valid() ? r.data()->elementSize() : 0; - }; - arrayRefType["isEmpty"] = +[](const scene::ArrayRef &r) -> bool { - return r.valid() ? r.data()->isEmpty() : true; - }; - arrayRefType["dim"] = +[](const scene::ArrayRef &r, size_t d) -> size_t { - return r.valid() ? r.data()->dim(d) : 0; - }; - arrayRefType["setData"] = - [](scene::ArrayRef &r, sol::table data, sol::this_state s) { - if (!r.valid()) - throw std::runtime_error("attempt to setData on invalid Array"); - arraySetDataFromLua(*r.data(), data, s); - }; - arrayRefType["getData"] = [](scene::ArrayRef &r, sol::this_state s) { - if (!r.valid()) - throw std::runtime_error("attempt to getData on invalid Array"); - return arrayGetDataAsLua(*r.data(), s); - }; + // Per-type Ref bindings — each lives in its own TU under bindings/refs/ + // so the nine `registerObjectMethodsOn` instantiations compile in + // parallel. + registerGeometryRef(tsd); + registerMaterialRef(tsd); + registerLightRef(tsd); + registerCameraRef(tsd); + registerSamplerRef(tsd); + registerSurfaceRef(tsd); + registerVolumeRef(tsd); + registerSpatialFieldRef(tsd); + registerArrayRef(tsd); } } // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ObjectMethodBindings.hpp b/tsd/src/tsd/scripting/bindings/ObjectMethodBindings.hpp index f11125a9e..4112934a6 100644 --- a/tsd/src/tsd/scripting/bindings/ObjectMethodBindings.hpp +++ b/tsd/src/tsd/scripting/bindings/ObjectMethodBindings.hpp @@ -12,15 +12,18 @@ // - For Object: returns &u (never null) // - For Ref: returns r.data() when valid, nullptr otherwise // -// Null-handling policy (follows Lua conventions): +// Each binding is a one-line trampoline that forwards to a free function +// in ObjectMethodImpls.cpp. The method body code is emitted exactly once +// for the whole TU instead of once per UserType, so per-type +// instantiation cost is dominated by sol2's call-wrapping machinery +// rather than by the optimizer chewing through duplicated bodies. +// +// Null-handling policy (implemented inside the free functions): // - Mutating operations (setParameter, setParameterArray): throw on null // - Read operations (getParameter, name, type, etc.): return nil/default // - Idempotent remove operations (removeParameter, etc.): silent no-op -#include "ArrayHelpers.hpp" -#include "ParameterHelpers.hpp" -#include "tsd/core/Token.hpp" -#include "tsd/scene/Parameter.hpp" +#include "ObjectMethodImpls.hpp" #include @@ -30,37 +33,19 @@ template void registerObjectMethodsOn(sol::usertype &ut, Accessor access) { ut["name"] = sol::property( - [access](UserType &u) -> std::string { - if (auto *obj = access(u)) - return obj->name(); - return ""; - }, + [access](UserType &u) { return objectGetName(access(u)); }, [access](UserType &u, const std::string &n) { - if (auto *obj = access(u)) - obj->setName(n.c_str()); + objectSetName(access(u), n); }); - ut["subtype"] = [access](UserType &u) -> std::string { - if (auto *obj = access(u)) - return obj->subtype().str(); - return ""; - }; - - ut["type"] = [access](UserType &u) -> anari::DataType { - if (auto *obj = access(u)) - return obj->type(); - return ANARI_UNKNOWN; - }; + ut["subtype"] = [access](UserType &u) { return objectGetSubtype(access(u)); }; + ut["type"] = [access](UserType &u) { return objectGetType(access(u)); }; ut["setParameter"] = [access](UserType &u, const std::string &name, sol::object value, sol::this_state s) { - auto *obj = access(u); - if (!obj) - throw std::runtime_error( - "attempt to set parameter on invalid reference"); - setParameterFromLua(obj, name, value); + objectSetParameter(access(u), name, value, s); }; ut["setParameterArray"] = sol::overload( @@ -69,11 +54,7 @@ void registerObjectMethodsOn(sol::usertype &ut, Accessor access) const std::string &typeStr, sol::table data, sol::this_state s) { - auto *obj = access(u); - if (!obj) - throw std::runtime_error( - "attempt to set parameter on invalid reference"); - return setParameterArrayFromLua(*obj, name, typeStr, data, s); + return objectSetParameterArray0(access(u), name, typeStr, data, s); }, [access](UserType &u, const std::string &name, @@ -81,12 +62,8 @@ void registerObjectMethodsOn(sol::usertype &ut, Accessor access) size_t items0, sol::table data, sol::this_state s) { - auto *obj = access(u); - if (!obj) - throw std::runtime_error( - "attempt to set parameter on invalid reference"); - return setParameterArrayFromLua( - *obj, name, typeStr, items0, 0, 0, data, s); + return objectSetParameterArray1( + access(u), name, typeStr, items0, data, s); }, [access](UserType &u, const std::string &name, @@ -95,12 +72,8 @@ void registerObjectMethodsOn(sol::usertype &ut, Accessor access) size_t items1, sol::table data, sol::this_state s) { - auto *obj = access(u); - if (!obj) - throw std::runtime_error( - "attempt to set parameter on invalid reference"); - return setParameterArrayFromLua( - *obj, name, typeStr, items0, items1, 0, data, s); + return objectSetParameterArray2( + access(u), name, typeStr, items0, items1, data, s); }, [access](UserType &u, const std::string &name, @@ -110,86 +83,55 @@ void registerObjectMethodsOn(sol::usertype &ut, Accessor access) size_t items2, sol::table data, sol::this_state s) { - auto *obj = access(u); - if (!obj) - throw std::runtime_error( - "attempt to set parameter on invalid reference"); - return setParameterArrayFromLua( - *obj, name, typeStr, items0, items1, items2, data, s); + return objectSetParameterArray3( + access(u), name, typeStr, items0, items1, items2, data, s); }); - ut["getParameter"] = [access](UserType &u, - const std::string &name, - sol::this_state s) -> sol::object { - auto *obj = access(u); - if (!obj) - return sol::lua_nil; - return getParameterAsLua(sol::state_view(s), obj, name); - }; + ut["getParameter"] = + [access](UserType &u, const std::string &name, sol::this_state s) { + return objectGetParameter(access(u), name, s); + }; - ut["parameter"] = [access](UserType &u, - const std::string &name) -> const scene::Parameter * { - auto *obj = access(u); - if (!obj) - return nullptr; - return obj->parameter(core::Token(name)); + ut["parameter"] = [access](UserType &u, const std::string &name) { + return objectGetParameterPtr(access(u), name); }; ut["removeParameter"] = [access](UserType &u, const std::string &name) { - if (auto *obj = access(u)) - obj->removeParameter(core::Token(name)); + objectRemoveParameter(access(u), name); }; ut["removeAllParameters"] = [access](UserType &u) { - if (auto *obj = access(u)) - obj->removeAllParameters(); + objectRemoveAllParameters(access(u)); }; - ut["numParameters"] = [access](UserType &u) -> size_t { - if (auto *obj = access(u)) - return obj->numParameters(); - return 0; + ut["numParameters"] = [access](UserType &u) { + return objectNumParameters(access(u)); }; - ut["parameterNameAt"] = [access](UserType &u, size_t i) -> const char * { - if (auto *obj = access(u)) - return obj->parameterNameAt(i); - return ""; + ut["parameterNameAt"] = [access](UserType &u, size_t i) { + return objectParameterNameAt(access(u), i); }; - // Metadata ut["setMetadata"] = [access](UserType &u, const std::string &key, sol::object value) { - if (auto *obj = access(u)) - setMetadataFromLua(obj, key, value); + objectSetMetadata(access(u), key, value); }; - ut["getMetadata"] = [access](UserType &u, - const std::string &key, - sol::this_state s) -> sol::object { - auto *obj = access(u); - if (!obj) - return sol::lua_nil; - return getMetadataAsLua(sol::state_view(s), obj, key); - }; + ut["getMetadata"] = + [access](UserType &u, const std::string &key, sol::this_state s) { + return objectGetMetadata(access(u), key, s); + }; ut["removeMetadata"] = [access](UserType &u, const std::string &key) { - if (auto *obj = access(u)) - obj->removeMetadata(key); + objectRemoveMetadata(access(u), key); }; - ut["numMetadata"] = [access](UserType &u) -> size_t { - if (auto *obj = access(u)) - return obj->numMetadata(); - return 0; + ut["numMetadata"] = [access](UserType &u) { + return objectNumMetadata(access(u)); }; - ut["getMetadataName"] = [access](UserType &u, size_t i) -> const char * { - if (auto *obj = access(u)) { - const char *n = obj->getMetadataName(i); - return n ? n : ""; - } - return ""; + ut["getMetadataName"] = [access](UserType &u, size_t i) { + return objectGetMetadataName(access(u), i); }; } diff --git a/tsd/src/tsd/scripting/bindings/ObjectMethodImpls.cpp b/tsd/src/tsd/scripting/bindings/ObjectMethodImpls.cpp new file mode 100644 index 000000000..36ef769d4 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/ObjectMethodImpls.cpp @@ -0,0 +1,187 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "ObjectMethodImpls.hpp" + +#include "ArrayHelpers.hpp" +#include "ParameterHelpers.hpp" +#include "tsd/core/Token.hpp" +#include "tsd/scene/Object.hpp" +#include "tsd/scene/Parameter.hpp" +#include "tsd/scene/objects/Array.hpp" + +#include + +#include + +namespace tsd::scripting { + +// Null-handling policy: +// - Mutating operations (setParameter, setParameterArray, set*): throw +// when obj is null. +// - Read operations (get*, num*, name*): return empty/default values. +// - Idempotent remove operations: silent no-op when obj is null. + +namespace { + +[[noreturn]] void throwInvalidRef() +{ + throw std::runtime_error("attempt to set parameter on invalid reference"); +} + +} // namespace + +std::string objectGetName(scene::Object *obj) +{ + return obj ? std::string(obj->name()) : std::string(); +} + +void objectSetName(scene::Object *obj, const std::string &n) +{ + if (obj) + obj->setName(n.c_str()); +} + +std::string objectGetSubtype(scene::Object *obj) +{ + return obj ? obj->subtype().str() : std::string(); +} + +anari::DataType objectGetType(scene::Object *obj) +{ + return obj ? obj->type() : ANARI_UNKNOWN; +} + +void objectSetParameter(scene::Object *obj, + const std::string &name, + sol::object value, + sol::this_state /*s*/) +{ + if (!obj) + throwInvalidRef(); + setParameterFromLua(obj, name, value); +} + +scene::ArrayRef objectSetParameterArray0(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + sol::table data, + sol::this_state s) +{ + if (!obj) + throwInvalidRef(); + return setParameterArrayFromLua(*obj, name, typeStr, data, s); +} + +scene::ArrayRef objectSetParameterArray1(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + size_t items0, + sol::table data, + sol::this_state s) +{ + if (!obj) + throwInvalidRef(); + return setParameterArrayFromLua(*obj, name, typeStr, items0, 0, 0, data, s); +} + +scene::ArrayRef objectSetParameterArray2(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + size_t items0, + size_t items1, + sol::table data, + sol::this_state s) +{ + if (!obj) + throwInvalidRef(); + return setParameterArrayFromLua( + *obj, name, typeStr, items0, items1, 0, data, s); +} + +scene::ArrayRef objectSetParameterArray3(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + size_t items0, + size_t items1, + size_t items2, + sol::table data, + sol::this_state s) +{ + if (!obj) + throwInvalidRef(); + return setParameterArrayFromLua( + *obj, name, typeStr, items0, items1, items2, data, s); +} + +sol::object objectGetParameter( + scene::Object *obj, const std::string &name, sol::this_state s) +{ + if (!obj) + return sol::lua_nil; + return getParameterAsLua(sol::state_view(s), obj, name); +} + +const scene::Parameter *objectGetParameterPtr( + scene::Object *obj, const std::string &name) +{ + return obj ? obj->parameter(core::Token(name)) : nullptr; +} + +void objectRemoveParameter(scene::Object *obj, const std::string &name) +{ + if (obj) + obj->removeParameter(core::Token(name)); +} + +void objectRemoveAllParameters(scene::Object *obj) +{ + if (obj) + obj->removeAllParameters(); +} + +size_t objectNumParameters(scene::Object *obj) +{ + return obj ? obj->numParameters() : 0; +} + +const char *objectParameterNameAt(scene::Object *obj, size_t i) +{ + return obj ? obj->parameterNameAt(i) : ""; +} + +void objectSetMetadata( + scene::Object *obj, const std::string &key, sol::object value) +{ + if (obj) + setMetadataFromLua(obj, key, value); +} + +sol::object objectGetMetadata( + scene::Object *obj, const std::string &key, sol::this_state s) +{ + if (!obj) + return sol::lua_nil; + return getMetadataAsLua(sol::state_view(s), obj, key); +} + +void objectRemoveMetadata(scene::Object *obj, const std::string &key) +{ + if (obj) + obj->removeMetadata(key); +} + +size_t objectNumMetadata(scene::Object *obj) +{ + return obj ? obj->numMetadata() : 0; +} + +const char *objectGetMetadataName(scene::Object *obj, size_t i) +{ + if (!obj) + return ""; + const char *n = obj->getMetadataName(i); + return n ? n : ""; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ObjectMethodImpls.hpp b/tsd/src/tsd/scripting/bindings/ObjectMethodImpls.hpp new file mode 100644 index 000000000..c9a92ce7d --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/ObjectMethodImpls.hpp @@ -0,0 +1,84 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Free-function implementations of every method that +// `registerObjectMethodsOn` binds onto a sol2 usertype. +// +// All methods take `scene::Object *` (possibly null) and handle the +// null case internally per the policy documented in +// ObjectMethodBindings.hpp. This keeps the template trampoline in +// that header trivial — body code is emitted exactly once in +// ObjectMethodImpls.cpp, not per UserType. + +#include "tsd/scene/objects/Array.hpp" + +#include + +#include + +#include + +namespace tsd::scene { +struct Object; +class Parameter; +} // namespace tsd::scene + +namespace tsd::scripting { + +std::string objectGetName(scene::Object *obj); +void objectSetName(scene::Object *obj, const std::string &n); +std::string objectGetSubtype(scene::Object *obj); +anari::DataType objectGetType(scene::Object *obj); + +void objectSetParameter(scene::Object *obj, + const std::string &name, + sol::object value, + sol::this_state s); + +scene::ArrayRef objectSetParameterArray0(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + sol::table data, + sol::this_state s); +scene::ArrayRef objectSetParameterArray1(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + size_t items0, + sol::table data, + sol::this_state s); +scene::ArrayRef objectSetParameterArray2(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + size_t items0, + size_t items1, + sol::table data, + sol::this_state s); +scene::ArrayRef objectSetParameterArray3(scene::Object *obj, + const std::string &name, + const std::string &typeStr, + size_t items0, + size_t items1, + size_t items2, + sol::table data, + sol::this_state s); + +sol::object objectGetParameter( + scene::Object *obj, const std::string &name, sol::this_state s); +const scene::Parameter *objectGetParameterPtr( + scene::Object *obj, const std::string &name); +void objectRemoveParameter(scene::Object *obj, const std::string &name); +void objectRemoveAllParameters(scene::Object *obj); +size_t objectNumParameters(scene::Object *obj); +const char *objectParameterNameAt(scene::Object *obj, size_t i); + +void objectSetMetadata( + scene::Object *obj, const std::string &key, sol::object value); +sol::object objectGetMetadata( + scene::Object *obj, const std::string &key, sol::this_state s); +void objectRemoveMetadata(scene::Object *obj, const std::string &key); +size_t objectNumMetadata(scene::Object *obj); +const char *objectGetMetadataName(scene::Object *obj, size_t i); + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ObjectPoolRefRegister.hpp b/tsd/src/tsd/scripting/bindings/ObjectPoolRefRegister.hpp new file mode 100644 index 000000000..dc77cf3b8 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/ObjectPoolRefRegister.hpp @@ -0,0 +1,43 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Shared helper: registers the common ObjectPoolRef usertype +// (constructor, validity, index, to_string) and forwards all Object +// methods via ObjectMethodBindings. Each per-type TU instantiates this +// for exactly one T and then layers on type-specific accessors. + +#include "ObjectMethodBindings.hpp" +#include "tsd/scene/Object.hpp" + +#include +#include + +namespace tsd::scripting { + +template +auto registerObjectPoolRef(sol::table &tsd, const char *name) +{ + using Ref = scene::ObjectPoolRef; + auto refType = tsd.new_usertype( + name, + sol::no_constructor, + "valid", + &Ref::valid, + "index", + [](const Ref &r) -> size_t { return r.index(); }, + sol::meta_function::to_string, + [name](const Ref &r) { + if (!r.valid()) + return fmt::format("{}(invalid)", name); + return fmt::format("{}({})", name, r.index()); + }); + + registerObjectMethodsOn(refType, + [](Ref &r) -> scene::Object * { return r.valid() ? r.data() : nullptr; }); + + return refType; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ObjectRefBindings.hpp b/tsd/src/tsd/scripting/bindings/ObjectRefBindings.hpp new file mode 100644 index 000000000..c04909236 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/ObjectRefBindings.hpp @@ -0,0 +1,26 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Per-type entry points for registering ObjectPoolRef usertypes. +// Each implementation lives in its own TU under bindings/refs/ so the +// nine sol2 instantiations of `registerObjectMethodsOn` (the heaviest +// template in the bindings) compile in parallel instead of serialising +// through one giant ObjectBindings.cpp. + +#include + +namespace tsd::scripting { + +void registerGeometryRef(sol::table &tsd); +void registerMaterialRef(sol::table &tsd); +void registerLightRef(sol::table &tsd); +void registerCameraRef(sol::table &tsd); +void registerSamplerRef(sol::table &tsd); +void registerSurfaceRef(sol::table &tsd); +void registerVolumeRef(sol::table &tsd); +void registerSpatialFieldRef(sol::table &tsd); +void registerArrayRef(sol::table &tsd); + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ObjectUsertype.cpp b/tsd/src/tsd/scripting/bindings/ObjectUsertype.cpp new file mode 100644 index 000000000..f55fca7d8 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/ObjectUsertype.cpp @@ -0,0 +1,27 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +// Registers the base `scene::Object` usertype. Sits in its own TU because +// the `registerObjectMethodsOn` instantiation for `scene::Object` is one +// of the two heaviest sol2 templates in the bindings (the other nine +// instantiations are the per-type Refs under refs/). + +#include "ObjectMethodBindings.hpp" +#include "ObjectUsertype.hpp" +#include "tsd/scene/Object.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerObjectUsertype(sol::table &tsd) +{ + auto objectType = tsd.new_usertype( + "Object", sol::no_constructor, "index", &scene::Object::index); + + registerObjectMethodsOn( + objectType, [](scene::Object &o) -> scene::Object * { return &o; }); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/ObjectUsertype.hpp b/tsd/src/tsd/scripting/bindings/ObjectUsertype.hpp new file mode 100644 index 000000000..5e3659655 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/ObjectUsertype.hpp @@ -0,0 +1,12 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace tsd::scripting { + +void registerObjectUsertype(sol::table &tsd); + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/SceneUsertype.hpp b/tsd/src/tsd/scripting/bindings/SceneUsertype.hpp new file mode 100644 index 000000000..72062c566 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/SceneUsertype.hpp @@ -0,0 +1,27 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Per-group entry points for registering methods on the +// `sol::usertype` object. Each group lives in its own TU +// under bindings/scene/ so the ~46 method bindings parallelise across +// cores instead of serialising through one giant CoreBindings.cpp. +// +// The usertype itself is created once by registerContextBindings() +// (with sol::no_constructor / constructors), then each register* call +// layers methods onto it. + +#include "tsd/scene/Scene.hpp" + +#include + +namespace tsd::scripting { + +void registerSceneCreators(sol::usertype &sceneType); +void registerSceneAccessors(sol::usertype &sceneType); +void registerSceneIteration(sol::usertype &sceneType); +void registerSceneLayers(sol::usertype &sceneType); +void registerSceneNodes(sol::usertype &sceneType); + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/math/Float2.cpp b/tsd/src/tsd/scripting/bindings/math/Float2.cpp new file mode 100644 index 000000000..921e09bac --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/math/Float2.cpp @@ -0,0 +1,28 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../MathUsertype.hpp" +#include "RegisterVec.hpp" +#include "tsd/core/TSDMath.hpp" + +#include +#include + +namespace tsd::scripting { + +void registerFloat2Usertype(sol::table &tsd) +{ + auto t = tsd.new_usertype("float2", + sol::constructors(), + "x", + &math::float2::x, + "y", + &math::float2::y, + sol::meta_function::to_string, + [](const math::float2 &v) { + return fmt::format("float2({}, {})", v.x, v.y); + }); + registerVecArithmetic(t); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/math/Float3.cpp b/tsd/src/tsd/scripting/bindings/math/Float3.cpp new file mode 100644 index 000000000..90001053f --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/math/Float3.cpp @@ -0,0 +1,30 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../MathUsertype.hpp" +#include "RegisterVec.hpp" +#include "tsd/core/TSDMath.hpp" + +#include +#include + +namespace tsd::scripting { + +void registerFloat3Usertype(sol::table &tsd) +{ + auto t = tsd.new_usertype("float3", + sol::constructors(), + "x", + &math::float3::x, + "y", + &math::float3::y, + "z", + &math::float3::z, + sol::meta_function::to_string, + [](const math::float3 &v) { + return fmt::format("float3({}, {}, {})", v.x, v.y, v.z); + }); + registerVecArithmetic(t); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/math/Float4.cpp b/tsd/src/tsd/scripting/bindings/math/Float4.cpp new file mode 100644 index 000000000..5aae7ffbe --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/math/Float4.cpp @@ -0,0 +1,33 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../MathUsertype.hpp" +#include "RegisterVec.hpp" +#include "tsd/core/TSDMath.hpp" + +#include +#include + +namespace tsd::scripting { + +void registerFloat4Usertype(sol::table &tsd) +{ + auto t = tsd.new_usertype("float4", + sol::constructors(), + "x", + &math::float4::x, + "y", + &math::float4::y, + "z", + &math::float4::z, + "w", + &math::float4::w, + sol::meta_function::to_string, + [](const math::float4 &v) { + return fmt::format("float4({}, {}, {}, {})", v.x, v.y, v.z, v.w); + }); + registerVecArithmetic(t); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/math/Mat3.cpp b/tsd/src/tsd/scripting/bindings/math/Mat3.cpp new file mode 100644 index 000000000..935d6f05c --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/math/Mat3.cpp @@ -0,0 +1,47 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../MathUsertype.hpp" +#include "tsd/core/TSDMath.hpp" + +#include +#include + +#include + +namespace tsd::scripting { + +void registerMat3Usertype(sol::table &tsd) +{ + tsd.new_usertype( + "mat3", + sol::constructors(), + sol::meta_function::index, + [](sol::this_state L, const math::mat3 &m, sol::object key) -> sol::object { + if (key.is()) { + int i = key.as(); + if (i < 0 || i > 2) + throw std::out_of_range("mat3 index must be 0, 1, or 2"); + return sol::make_object(L, m[i]); + } + if (key.is() && key.as() == "identity") + return sol::make_object(L, math::IDENTITY_MAT3); + return sol::lua_nil; + }, + sol::meta_function::new_index, + [](math::mat3 &m, int i, const math::float3 &v) { + if (i < 0 || i > 2) + throw std::out_of_range("mat3 index must be 0, 1, or 2"); + m[i] = v; + }, + sol::meta_function::to_string, + [](const math::mat3 &m) { + return fmt::format("mat3({}, {}, {})", + fmt::format("float3({}, {}, {})", m[0].x, m[0].y, m[0].z), + fmt::format("float3({}, {}, {})", m[1].x, m[1].y, m[1].z), + fmt::format("float3({}, {}, {})", m[2].x, m[2].y, m[2].z)); + }); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/math/Mat4.cpp b/tsd/src/tsd/scripting/bindings/math/Mat4.cpp new file mode 100644 index 000000000..445e216b6 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/math/Mat4.cpp @@ -0,0 +1,58 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../MathUsertype.hpp" +#include "tsd/core/TSDMath.hpp" + +#include +#include + +#include + +namespace tsd::scripting { + +void registerMat4Usertype(sol::table &tsd) +{ + tsd.new_usertype("mat4", + sol::constructors(), + sol::meta_function::multiplication, + sol::overload([](const math::mat4 &a, + const math::mat4 &b) { return math::mul(a, b); }, + [](const math::mat4 &a, const math::float4 &v) { + return math::mul(a, v); + }), + sol::meta_function::index, + [](sol::this_state L, const math::mat4 &m, sol::object key) -> sol::object { + if (key.is()) { + int i = key.as(); + if (i < 0 || i > 3) + throw std::out_of_range("mat4 index must be 0, 1, 2, or 3"); + return sol::make_object(L, m[i]); + } + if (key.is() && key.as() == "identity") + return sol::make_object(L, math::IDENTITY_MAT4); + return sol::lua_nil; + }, + sol::meta_function::new_index, + [](math::mat4 &m, int i, const math::float4 &v) { + if (i < 0 || i > 3) + throw std::out_of_range("mat4 index must be 0, 1, 2, or 3"); + m[i] = v; + }, + sol::meta_function::to_string, + [](const math::mat4 &m) { + return fmt::format("mat4({}, {}, {}, {})", + fmt::format( + "float4({}, {}, {}, {})", m[0].x, m[0].y, m[0].z, m[0].w), + fmt::format( + "float4({}, {}, {}, {})", m[1].x, m[1].y, m[1].z, m[1].w), + fmt::format( + "float4({}, {}, {}, {})", m[2].x, m[2].y, m[2].z, m[2].w), + fmt::format( + "float4({}, {}, {}, {})", m[3].x, m[3].y, m[3].z, m[3].w)); + }); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/math/RegisterVec.hpp b/tsd/src/tsd/scripting/bindings/math/RegisterVec.hpp new file mode 100644 index 000000000..3dfb391b2 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/math/RegisterVec.hpp @@ -0,0 +1,46 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +// Shared arithmetic-operator registration for the vector usertypes +// (float2/3/4). Each per-type TU includes this and applies it to its +// own sol::usertype. + +#include + +#include + +namespace tsd::scripting { + +template +void registerVecArithmetic(sol::usertype &ut) +{ + ut[sol::meta_function::addition] = [](const T &a, const T &b) { + return a + b; + }; + ut[sol::meta_function::subtraction] = [](const T &a, const T &b) { + return a - b; + }; + ut[sol::meta_function::multiplication] = + [](sol::object lhs, sol::object rhs) -> T { + if (lhs.is() && rhs.is()) + return lhs.as() * rhs.as(); + if (lhs.is() && rhs.is()) + return lhs.as() * static_cast(rhs.as()); + if (lhs.is() && rhs.is()) + return static_cast(lhs.as()) * rhs.as(); + throw std::runtime_error("invalid operand types for *"); + }; + ut[sol::meta_function::division] = + [](sol::object lhs, sol::object rhs) -> T { + if (lhs.is() && rhs.is()) + return lhs.as() / rhs.as(); + if (lhs.is() && rhs.is()) + return lhs.as() / static_cast(rhs.as()); + throw std::runtime_error("invalid operand types for /"); + }; + ut[sol::meta_function::unary_minus] = [](const T &a) { return -a; }; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/ArrayRef.cpp b/tsd/src/tsd/scripting/bindings/refs/ArrayRef.cpp new file mode 100644 index 000000000..c76f4c8f3 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/ArrayRef.cpp @@ -0,0 +1,48 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ArrayHelpers.hpp" +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/objects/Array.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +#include + +namespace tsd::scripting { + +void registerArrayRef(sol::table &tsd) +{ + auto arrayRefType = registerObjectPoolRef(tsd, "Array"); + + arrayRefType["elementType"] = +[](const scene::ArrayRef &r) -> anari::DataType { + return r.valid() ? r.data()->elementType() : ANARI_UNKNOWN; + }; + arrayRefType["size"] = +[](const scene::ArrayRef &r) -> size_t { + return r.valid() ? r.data()->size() : 0; + }; + arrayRefType["elementSize"] = +[](const scene::ArrayRef &r) -> size_t { + return r.valid() ? r.data()->elementSize() : 0; + }; + arrayRefType["isEmpty"] = +[](const scene::ArrayRef &r) -> bool { + return r.valid() ? r.data()->isEmpty() : true; + }; + arrayRefType["dim"] = +[](const scene::ArrayRef &r, size_t d) -> size_t { + return r.valid() ? r.data()->dim(d) : 0; + }; + arrayRefType["setData"] = + [](scene::ArrayRef &r, sol::table data, sol::this_state s) { + if (!r.valid()) + throw std::runtime_error("attempt to setData on invalid Array"); + arraySetDataFromLua(*r.data(), data, s); + }; + arrayRefType["getData"] = [](scene::ArrayRef &r, sol::this_state s) { + if (!r.valid()) + throw std::runtime_error("attempt to getData on invalid Array"); + return arrayGetDataAsLua(*r.data(), s); + }; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/CameraRef.cpp b/tsd/src/tsd/scripting/bindings/refs/CameraRef.cpp new file mode 100644 index 000000000..3024da0d3 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/CameraRef.cpp @@ -0,0 +1,18 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/objects/Camera.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerCameraRef(sol::table &tsd) +{ + registerObjectPoolRef(tsd, "Camera"); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/GeometryRef.cpp b/tsd/src/tsd/scripting/bindings/refs/GeometryRef.cpp new file mode 100644 index 000000000..ab1ea0000 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/GeometryRef.cpp @@ -0,0 +1,18 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/objects/Geometry.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerGeometryRef(sol::table &tsd) +{ + registerObjectPoolRef(tsd, "Geometry"); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/LightRef.cpp b/tsd/src/tsd/scripting/bindings/refs/LightRef.cpp new file mode 100644 index 000000000..4497ad9cb --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/LightRef.cpp @@ -0,0 +1,18 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/objects/Light.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerLightRef(sol::table &tsd) +{ + registerObjectPoolRef(tsd, "Light"); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/MaterialRef.cpp b/tsd/src/tsd/scripting/bindings/refs/MaterialRef.cpp new file mode 100644 index 000000000..6341bff56 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/MaterialRef.cpp @@ -0,0 +1,18 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/objects/Material.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerMaterialRef(sol::table &tsd) +{ + registerObjectPoolRef(tsd, "Material"); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/SamplerRef.cpp b/tsd/src/tsd/scripting/bindings/refs/SamplerRef.cpp new file mode 100644 index 000000000..ed5103f86 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/SamplerRef.cpp @@ -0,0 +1,18 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/objects/Sampler.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerSamplerRef(sol::table &tsd) +{ + registerObjectPoolRef(tsd, "Sampler"); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/SpatialFieldRef.cpp b/tsd/src/tsd/scripting/bindings/refs/SpatialFieldRef.cpp new file mode 100644 index 000000000..e5677ff45 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/SpatialFieldRef.cpp @@ -0,0 +1,26 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/objects/SpatialField.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerSpatialFieldRef(sol::table &tsd) +{ + auto fieldRefType = + registerObjectPoolRef(tsd, "SpatialField"); + + fieldRefType["computeValueRange"] = + +[](scene::SpatialFieldRef &r) -> math::float2 { + if (!r.valid()) + return math::float2(0.f); + return r.data()->computeValueRange(); + }; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/SurfaceRef.cpp b/tsd/src/tsd/scripting/bindings/refs/SurfaceRef.cpp new file mode 100644 index 000000000..155af501d --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/SurfaceRef.cpp @@ -0,0 +1,32 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scene/objects/Geometry.hpp" +#include "tsd/scene/objects/Material.hpp" +#include "tsd/scene/objects/Surface.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerSurfaceRef(sol::table &tsd) +{ + auto surfaceRefType = registerObjectPoolRef(tsd, "Surface"); + + surfaceRefType["geometry"] = +[](scene::SurfaceRef &r) -> scene::Geometry * { + if (!r.valid()) + return nullptr; + return r.data()->parameterValueAsObject("geometry"); + }; + surfaceRefType["material"] = +[](scene::SurfaceRef &r) -> scene::Material * { + if (!r.valid()) + return nullptr; + return r.data()->parameterValueAsObject("material"); + }; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/refs/VolumeRef.cpp b/tsd/src/tsd/scripting/bindings/refs/VolumeRef.cpp new file mode 100644 index 000000000..eb89148c3 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/refs/VolumeRef.cpp @@ -0,0 +1,27 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ObjectPoolRefRegister.hpp" +#include "../ObjectRefBindings.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scene/objects/SpatialField.hpp" +#include "tsd/scene/objects/Volume.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerVolumeRef(sol::table &tsd) +{ + auto volumeRefType = registerObjectPoolRef(tsd, "Volume"); + + volumeRefType["spatialField"] = + +[](scene::VolumeRef &r) -> scene::SpatialField * { + if (!r.valid()) + return nullptr; + return r.data()->parameterValueAsObject("value"); + }; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/scene/Accessors.cpp b/tsd/src/tsd/scripting/bindings/scene/Accessors.cpp new file mode 100644 index 000000000..01462e731 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/scene/Accessors.cpp @@ -0,0 +1,56 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../SceneUsertype.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scene/objects/Array.hpp" +#include "tsd/scene/objects/Camera.hpp" +#include "tsd/scene/objects/Geometry.hpp" +#include "tsd/scene/objects/Light.hpp" +#include "tsd/scene/objects/Material.hpp" +#include "tsd/scene/objects/Sampler.hpp" +#include "tsd/scene/objects/SpatialField.hpp" +#include "tsd/scene/objects/Surface.hpp" +#include "tsd/scene/objects/Volume.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerSceneAccessors(sol::usertype &sceneType) +{ + sceneType["getGeometry"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getMaterial"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getLight"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getCamera"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getSurface"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getArray"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getVolume"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getSampler"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["getSpatialField"] = [](scene::Scene &s, size_t i) { + return s.getObject(i); + }; + sceneType["numberOfObjects"] = + [](scene::Scene &s, anari::DataType type) -> size_t { + return s.numberOfObjects(type); + }; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/scene/Creators.cpp b/tsd/src/tsd/scripting/bindings/scene/Creators.cpp new file mode 100644 index 000000000..026f4cde3 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/scene/Creators.cpp @@ -0,0 +1,160 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ArrayHelpers.hpp" +#include "../ParameterHelpers.hpp" +#include "../SceneUsertype.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scene/objects/Array.hpp" +#include "tsd/scene/objects/Camera.hpp" +#include "tsd/scene/objects/Geometry.hpp" +#include "tsd/scene/objects/Light.hpp" +#include "tsd/scene/objects/Material.hpp" +#include "tsd/scene/objects/Sampler.hpp" +#include "tsd/scene/objects/SpatialField.hpp" +#include "tsd/scene/objects/Surface.hpp" +#include "tsd/scene/objects/Volume.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +#include + +namespace tsd::scripting { + +namespace { + +scene::ArrayRef createArrayFromLua(scene::Scene &scene, + const std::string &typeStr, + size_t items0, + size_t items1, + size_t items2) +{ + return scene.createArray( + arrayTypeFromString(typeStr), items0, items1, items2); +} + +scene::ArrayRef createArrayFromLua(scene::Scene &scene, + const std::string &typeStr, + size_t items0, + size_t items1, + size_t items2, + sol::table data, + sol::this_state s) +{ + const auto elemType = arrayTypeFromString(typeStr); + const bool isObj = anari::isObject(elemType); + + if (items0 == 0) { + if (isObj) { + items0 = data.size(); + } else { + inferArrayDimsFromLuaData(data, elemType, items0, items1, items2); + } + } + + auto arr = scene.createArray(elemType, items0, items1, items2); + if (!arr.valid()) + throw std::runtime_error("createArray: failed to create array"); + + if (isObj) + arraySetObjectsFromLua(*arr.data(), data); + else + arraySetDataFromLua(*arr.data(), data, s); + + return arr; +} + +template +auto makeCreateBinding() +{ + return [](scene::Scene &s, + const std::string &subtype, + sol::optional params) { + auto ref = s.createObject(core::Token(subtype)); + if (params) + applyParameterTable(ref.data(), *params); + return ref; + }; +} + +} // namespace + +void registerSceneCreators(sol::usertype &sceneType) +{ + sceneType["createGeometry"] = makeCreateBinding(); + sceneType["createMaterial"] = makeCreateBinding(); + sceneType["createLight"] = makeCreateBinding(); + sceneType["createCamera"] = makeCreateBinding(); + sceneType["createSampler"] = makeCreateBinding(); + sceneType["createVolume"] = makeCreateBinding(); + sceneType["createSpatialField"] = makeCreateBinding(); + + sceneType["createSurface"] = [](scene::Scene &s, + const std::string &name, + scene::GeometryRef g, + scene::MaterialRef m, + sol::optional params) { + auto ref = s.createSurface(name.c_str(), g, m); + if (params) + applyParameterTable(ref.data(), *params); + return ref; + }; + + sceneType["createArray"] = sol::overload( + // (typeStr, table) — infer dims from data + [](scene::Scene &s, + const std::string &typeStr, + sol::table data, + sol::this_state st) { + return createArrayFromLua(s, typeStr, 0, 0, 0, data, st); + }, + // (typeStr, items0) — empty 1D + [](scene::Scene &s, const std::string &typeStr, size_t items0) { + return createArrayFromLua(s, typeStr, items0, 0, 0); + }, + // (typeStr, items0, table) — 1D with data + [](scene::Scene &s, + const std::string &typeStr, + size_t items0, + sol::table data, + sol::this_state st) { + return createArrayFromLua(s, typeStr, items0, 0, 0, data, st); + }, + // (typeStr, items0, items1) — empty 2D + [](scene::Scene &s, + const std::string &typeStr, + size_t items0, + size_t items1) { + return createArrayFromLua(s, typeStr, items0, items1, 0); + }, + // (typeStr, items0, items1, table) — 2D with data + [](scene::Scene &s, + const std::string &typeStr, + size_t items0, + size_t items1, + sol::table data, + sol::this_state st) { + return createArrayFromLua(s, typeStr, items0, items1, 0, data, st); + }, + // (typeStr, items0, items1, items2) — empty 3D + [](scene::Scene &s, + const std::string &typeStr, + size_t items0, + size_t items1, + size_t items2) { + return createArrayFromLua(s, typeStr, items0, items1, items2); + }, + // (typeStr, items0, items1, items2, table) — 3D with data + [](scene::Scene &s, + const std::string &typeStr, + size_t items0, + size_t items1, + size_t items2, + sol::table data, + sol::this_state st) { + return createArrayFromLua(s, typeStr, items0, items1, items2, data, st); + }); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/scene/Iteration.cpp b/tsd/src/tsd/scripting/bindings/scene/Iteration.cpp new file mode 100644 index 000000000..ef1444bfb --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/scene/Iteration.cpp @@ -0,0 +1,53 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../SceneUsertype.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +namespace { + +template +auto makeForEach(F poolAccessor) +{ + return [poolAccessor](scene::Scene &s, sol::function fn) { + const auto &pool = poolAccessor(s.objectDB()); + for (size_t i = 0; i < pool.capacity(); i++) { + if (!pool.slot_empty(i)) { + sol::object result = fn(pool.at(i)); + if (result.is() && !result.as()) + break; + } + } + }; +} + +} // namespace + +void registerSceneIteration(sol::usertype &sceneType) +{ + sceneType["forEachGeometry"] = + makeForEach([](auto &db) -> auto & { return db.geometry; }); + sceneType["forEachMaterial"] = + makeForEach([](auto &db) -> auto & { return db.material; }); + sceneType["forEachSurface"] = + makeForEach([](auto &db) -> auto & { return db.surface; }); + sceneType["forEachLight"] = + makeForEach([](auto &db) -> auto & { return db.light; }); + sceneType["forEachCamera"] = + makeForEach([](auto &db) -> auto & { return db.camera; }); + sceneType["forEachVolume"] = + makeForEach([](auto &db) -> auto & { return db.volume; }); + sceneType["forEachSpatialField"] = + makeForEach([](auto &db) -> auto & { return db.field; }); + sceneType["forEachSampler"] = + makeForEach([](auto &db) -> auto & { return db.sampler; }); + sceneType["forEachArray"] = + makeForEach([](auto &db) -> auto & { return db.array; }); +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/scene/Layers.cpp b/tsd/src/tsd/scripting/bindings/scene/Layers.cpp new file mode 100644 index 000000000..44f69ad70 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/scene/Layers.cpp @@ -0,0 +1,69 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../SceneUsertype.hpp" +#include "tsd/core/Token.hpp" +#include "tsd/scene/Layer.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +namespace tsd::scripting { + +void registerSceneLayers(sol::usertype &sceneType) +{ + sceneType["addLayer"] = [](scene::Scene &s, const std::string &name) { + return s.addLayer(core::Token(name)); + }; + + sceneType["layer"] = sol::overload( + [](scene::Scene &s, const std::string &name) { + return s.layer(core::Token(name)); + }, + [](scene::Scene &s, size_t i) { return s.layer(i); }); + + sceneType["numberOfLayers"] = &scene::Scene::numberOfLayers; + sceneType["defaultLayer"] = &scene::Scene::defaultLayer; + sceneType["defaultMaterial"] = &scene::Scene::defaultMaterial; + + sceneType["removeLayer"] = sol::overload( + [](scene::Scene &s, const std::string &name) { + s.removeLayer(core::Token(name)); + }, + [](scene::Scene &s, scene::Layer *layer) { s.removeLayer(layer); }); + + sceneType["removeAllLayers"] = &scene::Scene::removeAllLayers; + + sceneType["layerIsActive"] = [](scene::Scene &s, const std::string &name) { + return s.layerIsActive(core::Token(name)); + }; + + sceneType["setLayerActive"] = + [](scene::Scene &s, const std::string &name, bool active) { + s.setLayerActive(core::Token(name), active); + }; + + sceneType["setAllLayersActive"] = &scene::Scene::setAllLayersActive; + + sceneType["setOnlyLayerActive"] = + [](scene::Scene &s, const std::string &name) { + s.setOnlyLayerActive(core::Token(name)); + }; + + sceneType["numberOfActiveLayers"] = &scene::Scene::numberOfActiveLayers; + + sceneType["signalLayerStructureChanged"] = + [](scene::Scene &s, scene::Layer *l) { + if (l) + s.signalLayerStructureChanged(l); + }; + + sceneType["signalLayerTransformChanged"] = + [](scene::Scene &s, scene::Layer *l) { + if (l) + s.signalLayerTransformChanged(l); + }; +} + +} // namespace tsd::scripting diff --git a/tsd/src/tsd/scripting/bindings/scene/Nodes.cpp b/tsd/src/tsd/scripting/bindings/scene/Nodes.cpp new file mode 100644 index 000000000..437944d66 --- /dev/null +++ b/tsd/src/tsd/scripting/bindings/scene/Nodes.cpp @@ -0,0 +1,79 @@ +// Copyright 2026 NVIDIA Corporation +// SPDX-License-Identifier: Apache-2.0 + +#include "../ArrayHelpers.hpp" +#include "../SceneUsertype.hpp" +#include "tsd/core/TSDMath.hpp" +#include "tsd/scene/Scene.hpp" +#include "tsd/scene/objects/Array.hpp" +#include "tsd/scripting/Sol2Helpers.hpp" + +#include + +#include + +namespace tsd::scripting { + +void registerSceneNodes(sol::usertype &sceneType) +{ + sceneType["insertChildNode"] = [](scene::Scene &s, + scene::LayerNodeRef parent, + const std::string &name) { + return s.insertChildNode(parent, name.c_str()); + }; + + sceneType["insertChildTransformNode"] = [](scene::Scene &s, + scene::LayerNodeRef parent, + const math::mat4 &xfm, + const std::string &name) { + return s.insertChildTransformNode(parent, xfm, name.c_str()); + }; + + sceneType["insertChildTransformArrayNode"] = sol::overload( + [](scene::Scene &s, + scene::LayerNodeRef parent, + scene::Array &a, + const std::string &name) { + return s.insertChildTransformArrayNode(parent, &a, name.c_str()); + }, + [](scene::Scene &s, + scene::LayerNodeRef parent, + scene::ArrayRef a, + const std::string &name) { + if (!a) + throw std::runtime_error( + "insertChildTransformArrayNode: invalid array"); + return s.insertChildTransformArrayNode(parent, a.data(), name.c_str()); + }); + + sceneType["insertObjectNode"] = [](scene::Scene &s, + scene::LayerNodeRef parent, + sol::object objArg, + sol::optional name) { + auto *obj = extractObjectPtr(objArg); + if (!obj) + throw std::runtime_error("insertObjectNode: invalid object argument"); + return s.insertChildObjectNode( + parent, obj->type(), obj->index(), name.value_or("").c_str()); + }; + + sceneType["removeObject"] = [](scene::Scene &s, sol::object objArg) { + auto *obj = extractObjectPtr(objArg); + if (obj) + s.removeObject(obj); + }; + + sceneType["removeAllObjects"] = &scene::Scene::removeAllObjects; + + sceneType["removeNode"] = sol::overload( + [](scene::Scene &s, scene::LayerNodeRef obj) { s.removeNode(obj); }, + [](scene::Scene &s, scene::LayerNodeRef obj, bool deleteObjects) { + s.removeNode(obj, deleteObjects); + }); + + sceneType["removeUnusedObjects"] = &scene::Scene::removeUnusedObjects; + sceneType["defragmentObjectStorage"] = &scene::Scene::defragmentObjectStorage; + sceneType["cleanupScene"] = &scene::Scene::cleanupScene; +} + +} // namespace tsd::scripting From fccc648a0154cec3f03cb9756685e70ed35f81d2 Mon Sep 17 00:00:00 2001 From: Thomas Arcila <134677+tarcila@users.noreply.github.com> Date: Thu, 28 May 2026 10:32:05 -0400 Subject: [PATCH 2/2] tsd/scripting: install sol2 exception handler and fix surfaced bugs C++ throws across the sol2 boundary now turn into Lua errors instead of crashing the process. Plus fixes uncovered by the now-running test suite. --- tsd/scripts/examples/render_scene.lua | 2 +- tsd/scripts/test_lua_api.lua | 10 +- tsd/src/tsd/scripting/LuaContext.cpp | 12 ++ .../scripting/bindings/AnimationBindings.cpp | 6 +- .../tsd/scripting/bindings/scene/Nodes.cpp | 13 +- tsd/src/tsd/scripting/tsd.lua | 117 +++++++++--------- 6 files changed, 93 insertions(+), 67 deletions(-) diff --git a/tsd/scripts/examples/render_scene.lua b/tsd/scripts/examples/render_scene.lua index 1123b3850..8cf9e56a3 100644 --- a/tsd/scripts/examples/render_scene.lua +++ b/tsd/scripts/examples/render_scene.lua @@ -131,7 +131,7 @@ for frame = 0, numFrames - 1 do local angle = tsd.radians(frame * 360.0 / numFrames) local rot = tsd.rotation(tsd.float3(0, 1, 0), angle) rootXfm:setAsTransform(rot) - scene:signalLayerChange(layer) + scene:signalLayerTransformChanged(layer) local filename = string.format("%s_%04d.png", outPrefix, frame) tsd.render.renderToFile(pipeline, samples, filename, width, height) diff --git a/tsd/scripts/test_lua_api.lua b/tsd/scripts/test_lua_api.lua index 12cc2af2a..89c4c118d 100644 --- a/tsd/scripts/test_lua_api.lua +++ b/tsd/scripts/test_lua_api.lua @@ -1594,12 +1594,12 @@ section("ForEach (Extended)") test("scene:forEachCamera", function() local s = tsd.createScene() + local before = 0 + s:forEachCamera(function(_) before = before + 1 end) s:createCamera("perspective") - local count = 0 - s:forEachCamera(function(c) - count = count + 1 - end) - assert(count == 1, "should iterate over 1 camera") + local after = 0 + s:forEachCamera(function(_) after = after + 1 end) + assert(after == before + 1, "forEachCamera should yield one more camera after create") end) test("scene:forEachVolume", function() diff --git a/tsd/src/tsd/scripting/LuaContext.cpp b/tsd/src/tsd/scripting/LuaContext.cpp index 0b4977407..7b9ea8586 100644 --- a/tsd/src/tsd/scripting/LuaContext.cpp +++ b/tsd/src/tsd/scripting/LuaContext.cpp @@ -40,6 +40,18 @@ LuaContext::LuaContext() : m_impl(std::make_unique()) sol::lib::io, sol::lib::utf8); + // Translate C++ exceptions thrown from bindings into Lua errors. Without + // this, sol2's default handler pushes a string and lets lua_error longjmp + // over live C++ destructors — undefined behavior that segfaults in practice. + m_impl->lua.set_exception_handler( + [](lua_State *L, + sol::optional maybe, + sol::string_view desc) { + const std::string msg = + maybe ? maybe->what() : std::string(desc); + return luaL_error(L, "%s", msg.c_str()); + }); + m_impl->lua.set_function("print", [this](sol::variadic_args va) { fmt::memory_buffer buf; bool first = true; diff --git a/tsd/src/tsd/scripting/bindings/AnimationBindings.cpp b/tsd/src/tsd/scripting/bindings/AnimationBindings.cpp index a42697c37..e2b22986d 100644 --- a/tsd/src/tsd/scripting/bindings/AnimationBindings.cpp +++ b/tsd/src/tsd/scripting/bindings/AnimationBindings.cpp @@ -72,7 +72,11 @@ void registerAnimationBindings(sol::table &tsd) "Animation", sol::no_constructor, "name", - &tsd::animation::Animation::name, + sol::property( + [](const tsd::animation::Animation &a) { return a.name(); }, + [](tsd::animation::Animation &a, const std::string &n) { + a.editableName() = n; + }), "addObjectParameterBinding", [](tsd::animation::Animation &a, sol::object target, diff --git a/tsd/src/tsd/scripting/bindings/scene/Nodes.cpp b/tsd/src/tsd/scripting/bindings/scene/Nodes.cpp index 437944d66..21cbeb1ea 100644 --- a/tsd/src/tsd/scripting/bindings/scene/Nodes.cpp +++ b/tsd/src/tsd/scripting/bindings/scene/Nodes.cpp @@ -66,12 +66,19 @@ void registerSceneNodes(sol::usertype &sceneType) sceneType["removeAllObjects"] = &scene::Scene::removeAllObjects; sceneType["removeNode"] = sol::overload( - [](scene::Scene &s, scene::LayerNodeRef obj) { s.removeNode(obj); }, + [](scene::Scene &s, scene::LayerNodeRef obj) { + if (obj.valid()) + s.removeNode(obj); + }, [](scene::Scene &s, scene::LayerNodeRef obj, bool deleteObjects) { - s.removeNode(obj, deleteObjects); + if (obj.valid()) + s.removeNode(obj, deleteObjects); }); - sceneType["removeUnusedObjects"] = &scene::Scene::removeUnusedObjects; + sceneType["removeUnusedObjects"] = + [](scene::Scene &s, sol::optional includeRenderersAndCameras) { + s.removeUnusedObjects(includeRenderersAndCameras.value_or(false)); + }; sceneType["defragmentObjectStorage"] = &scene::Scene::defragmentObjectStorage; sceneType["cleanupScene"] = &scene::Scene::cleanupScene; } diff --git a/tsd/src/tsd/scripting/tsd.lua b/tsd/src/tsd/scripting/tsd.lua index 39fe29a4e..13354dab7 100644 --- a/tsd/src/tsd/scripting/tsd.lua +++ b/tsd/src/tsd/scripting/tsd.lua @@ -716,13 +716,11 @@ function CameraSetup.new() end ---@class tsd.AnariDevice ---@field libraryName string # (read-only) +--- Render index handle. Construct via `tsd.render.createRenderIndex(scene, device)` +--- — there is no Lua-callable constructor (the C++ side requires a raw ANARI device handle). ---@class tsd.RenderIndex local RenderIndex = {} ----@overload fun(scene: tsd.Scene, device: any): tsd.RenderIndex ----@return tsd.RenderIndex -function RenderIndex.new(...) end - --- Bootstrap or rebuild this render index from the current scene snapshot. --- This does not register the render index for live scene updates. function RenderIndex:populate() end @@ -915,123 +913,124 @@ function tsd.viewer.clearActions() end tsd.io = {} --- Import an OBJ file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode, useDefaultMat: boolean) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode, useDefaultMat: boolean) function tsd.io.importOBJ(...) end --- Import a glTF file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importGLTF(...) end --- Import a PLY file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importPLY(...) end --- Import an HDRI environment map. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importHDRI(...) end --- Import a USD file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importUSD(...) end --- Import a PBRT v4 scene file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importPBRT(...) end --- Import a PDB (Protein Data Bank) file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importPDB(...) end --- Import an SWC (neuron morphology) file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importSWC(...) end --- Import an AGX file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importAGX(...) end --- Import via ASSIMP (supports many formats). ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode, flatten: boolean) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode, flatten: boolean) function tsd.io.importASSIMP(...) end --- Import an AXYZ point cloud file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importAXYZ(...) end --- Import a DLAF file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode, useDefaultMat: boolean) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode, useDefaultMat: boolean) function tsd.io.importDLAF(...) end --- Import an E57 point cloud file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importE57XYZ(...) end --- Import an EnSight Gold case file. --- Fields selects which variables to load (up to 4 ANARI attribute slots). --- Timestep selects which time step index to load (0-based). ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode, fields: string[]) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode, fields: string[], timestep: integer) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode, fields: string[]) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode, fields: string[], timestep: integer) function tsd.io.importENSIGHT(...) end --- Import an HSMESH file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importHSMESH(...) end --- Import an N-body simulation file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode, useDefaultMat: boolean) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode, useDefaultMat: boolean) function tsd.io.importNBODY(...) end --- Import POINTSBIN files (multi-file). ----@overload fun(scene: tsd.Scene, filepaths: string[]) ----@overload fun(scene: tsd.Scene, filepaths: string[], location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filepaths: string[]) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filepaths: string[], location: tsd.LayerNode) function tsd.io.importPOINTSBIN(...) end --- Import a PT file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importPT(...) end --- Import a Silo file (scene-level). Requires a location parameter. ---@param scene tsd.Scene +---@param anim tsd.AnimationManager ---@param filename string ---@param location tsd.LayerNode -function tsd.io.importSilo(scene, filename, location) end +function tsd.io.importSilo(scene, anim, filename, location) end --- Import an SMESH file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode, isAnimation: boolean) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode, isAnimation: boolean) function tsd.io.importSMESH(...) end --- Import a TRK track file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importTRK(...) end --- Import an XYZDP point cloud file. ----@overload fun(scene: tsd.Scene, filename: string) ----@overload fun(scene: tsd.Scene, filename: string, location: tsd.LayerNode) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, location: tsd.LayerNode) function tsd.io.importXYZDP(...) end --- Import a volume file (auto-detects format). @@ -1125,14 +1124,18 @@ function tsd.io.makeDefaultColorMap(scene, size) end --- Save a scene to a TSD file. --- When called with a state table, the file can be opened directly in --- tsdViewer with the correct device and camera position. +--- The animation-manager overloads also persist animation tracks. ---@overload fun(scene: tsd.Scene, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) ---@overload fun(scene: tsd.Scene, filename: string, state: table) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string, state: table) function tsd.io.saveScene(...) end --- Load a scene from a TSD file. ----@param scene tsd.Scene ----@param filename string -function tsd.io.loadScene(scene, filename) end +--- The animation-manager overload also loads animation tracks if present. +---@overload fun(scene: tsd.Scene, filename: string) +---@overload fun(scene: tsd.Scene, anim: tsd.AnimationManager, filename: string) +function tsd.io.loadScene(...) end -- tsd.render (RenderBindings.cpp) ----------------------------------------