diff --git a/.cargo/config.toml b/.cargo/config.toml index 463c7d15b85..018a72b488c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -11,3 +11,14 @@ inherits = "release" [profile.debug-tracy] inherits = "dev" + +[profile.small] +inherits = "release" +opt-level = "z" +codegen-units = 1 +# This saves like 2MB (from 52 to 50MB on formabble) +lto = true + +[profile.extra-small] +inherits = "small" +panic = "abort" \ No newline at end of file diff --git a/cmake/Platform.cmake b/cmake/Platform.cmake index 998f7cde97f..700a72832b7 100644 --- a/cmake/Platform.cmake +++ b/cmake/Platform.cmake @@ -24,10 +24,10 @@ if(APPLE) if(CMAKE_Swift_FLAGS) string(REGEX REPLACE "-target [^ ]+" "" CMAKE_Swift_FLAGS "${CMAKE_Swift_FLAGS}") endif() - + # Add the deployment target flag to Swift compiler options set(CMAKE_Swift_FLAGS "${CMAKE_Swift_FLAGS} ${deployment_target_flag}" CACHE STRING "Swift compiler flags" FORCE) - + # Find Swift compiler instead of hardcoding the Xcode path find_program(CMAKE_Swift_COMPILER swiftc REQUIRED) enable_language(Swift) @@ -44,10 +44,12 @@ if(APPLE) if(XCODE_SDK) string(REGEX MATCH "simulator" IS_SIMULATOR ${XCODE_SDK}) + if(IS_SIMULATOR) message(STATUS "Building for simulator: ${XCODE_SDK}") + # Generic simulator settings here - + # Optionally detect specific simulator type if(XCODE_SDK MATCHES "iphonesimulator") message(STATUS "iOS Simulator detected") @@ -121,6 +123,7 @@ endif() if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_compile_definitions(SH_DEBUG=1) endif() + if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") # define SH_RELWITHDEBINFO to enable some extra debug asserts add_compile_definitions(SH_RELWITHDEBINFO=1) @@ -205,12 +208,20 @@ endif() if(MSVC OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") set(WINDOWS_ABI "msvc") - # We can not keep iterators in memory without freeing with iterator debugging - # See SHTable/Set iterator internals + set(ITERATOR_DEBUG_LEVEL 0) + if(CMAKE_BUILD_TYPE MATCHES "Debug") - add_compile_definitions(_ITERATOR_DEBUG_LEVEL=1) - list(APPEND EXTERNAL_CMAKE_ARGS -DCMAKE_CXX_FLAGS="-D_ITERATOR_DEBUG_LEVEL=1") + set(ITERATOR_DEBUG_LEVEL 1) + endif() + + # This is required for ASAN to work correctly under LLVM/MSVC + if(USE_ASAN) + set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded) + set(ITERATOR_DEBUG_LEVEL 0) # Needed to match clang_rt.asan lib endif() + + add_compile_definitions(_ITERATOR_DEBUG_LEVEL=${ITERATOR_DEBUG_LEVEL}) + list(APPEND EXTERNAL_CMAKE_ARGS -DCMAKE_CXX_FLAGS="-D_ITERATOR_DEBUG_LEVEL=${ITERATOR_DEBUG_LEVEL}") else() set(WINDOWS_ABI "gnu") endif() @@ -322,6 +333,7 @@ if(USE_ASAN) $<$:-fno-omit-frame-pointer> $<$:-g> ) + if(CMAKE_GENERATOR STREQUAL "Xcode") add_link_options( -DBOOST_USE_ASAN @@ -364,6 +376,7 @@ if(USE_TSAN) $<$:-fsanitize=thread> $<$:-g> ) + if(CMAKE_GENERATOR STREQUAL "Xcode") add_link_options( -fsanitize=thread @@ -375,6 +388,7 @@ if(USE_TSAN) $<$:-g> ) endif() + if(USE_TSAN GREATER 1) add_compile_options( $<$:-O1> @@ -383,6 +397,7 @@ if(USE_TSAN) $<$:-O1> ) endif() + add_compile_definitions($<$:SH_USE_TSAN>) endif() @@ -431,3 +446,8 @@ else() set(LIB_PREFIX "lib") set(LIB_SUFFIX ".a") endif() + +if(MSVC OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") + # Workaround for tbb sometimes linking #pragma comment(lib, "tbb12.lib") + add_compile_definitions(__TBB_SOURCE_DIRECTLY_INCLUDED=1) +endif() diff --git a/cmake/Rust.cmake b/cmake/Rust.cmake index 1142afdc55c..dcb71347956 100644 --- a/cmake/Rust.cmake +++ b/cmake/Rust.cmake @@ -78,6 +78,22 @@ if(RUST_BUILD_TYPE STREQUAL "Debug") elseif(RUST_BUILD_TYPE STREQUAL "RelWithDebInfo") set(RUST_CARGO_FLAGS_INT --profile rel-with-deb-info) set(RUST_BUILD_SUBDIR_CONFIGURATION rel-with-deb-info) +elseif(RUST_BUILD_TYPE STREQUAL "Small") + set(RUST_CARGO_FLAGS_INT --profile small) + set(RUST_BUILD_SUBDIR_CONFIGURATION small) + list(APPEND RUST_FLAGS -Zlocation-detail=none) + list(APPEND RUSTC_FLAGS + -Zbuild-std=std + -Zbuild-std-features=optimize_for_size + ) +elseif(RUST_BUILD_TYPE STREQUAL "ExtraSmall") + set(RUST_CARGO_FLAGS_INT --profile extra-small) + set(RUST_BUILD_SUBDIR_CONFIGURATION extra-small) + list(APPEND RUST_FLAGS -Zlocation-detail=none) + list(APPEND RUSTC_FLAGS + -Zbuild-std=std,panic_abort + -Zbuild-std-features=optimize_for_size,panic_immediate_abort + ) else() set(RUST_CARGO_FLAGS_INT --release) set(RUST_BUILD_SUBDIR_CONFIGURATION release) diff --git a/deps/JoltPhysics b/deps/JoltPhysics index 5c9da139219..018df332c9d 160000 --- a/deps/JoltPhysics +++ b/deps/JoltPhysics @@ -1 +1 @@ -Subproject commit 5c9da139219779f3c4fbdea833373cce54a3dbc0 +Subproject commit 018df332c9dbc7e2fff9502799e77c2e2494809d diff --git a/lib/hot-reload.shs b/lib/hot-reload.shs index 282367f4b1d..923d9c5447a 100644 --- a/lib/hot-reload.shs +++ b/lib/hot-reload.shs @@ -15,7 +15,7 @@ [base-path script-path] | FS.Join = script-path-abs script-path-abs | Regex.Replace("\\\\" "\\\\\\\\") = script-path-escaped - script-path-abs | Log("Setting up hot reload for script") + [script-path-abs namespace] | Log("Setting up hot reload for script") [ "\"seq\":[" @@ -81,6 +81,7 @@ } Pure: true) ) = loader-ast Regex.Replace("""\{"id":\{"name":"loader"\}\}""" wrapper-name-id-json) + Regex.Replace("""\{"id":\{"name":"namespace"\}\}""" (["{\"str\": \"" namespace "\"}"] | String.Format)) Regex.Replace("""\{"id":\{"name":"script-path"\}\}""" (["{\"str\": \"" script-path-escaped "\"}"] | String.Format)) Regex.Replace(""""id":\{"name":"include-dirs"\}""" include-dirs-json) diff --git a/shards/core/CMakeLists.txt b/shards/core/CMakeLists.txt index a149d0f0347..c2aefd21da2 100644 --- a/shards/core/CMakeLists.txt +++ b/shards/core/CMakeLists.txt @@ -11,6 +11,7 @@ set(core_SOURCES taskflow.cpp pmr/shared_temp_allocator.cpp log_api.cpp + wires.cpp ) if(EMSCRIPTEN) diff --git a/shards/core/brancher.hpp b/shards/core/brancher.hpp index 843520b5ecb..a89affeaee0 100644 --- a/shards/core/brancher.hpp +++ b/shards/core/brancher.hpp @@ -29,14 +29,14 @@ struct Brancher { BranchFailureBehavior failureBehavior = BranchFailureBehavior::Everything; private: - std::unordered_map _collectedRequirements; + decltype(SHWire::requirements) _collectedRequirements; std::unordered_set _copyBySerialize; ExposedInfo _mergedRequirements; ExposedInfo _shared; mutable std::vector _cachedObjectTypes; public: - Brancher() { mesh->inheritLogContext = true; } + Brancher() { mesh->inheritLogContext = true; } ~Brancher() { cleanup(nullptr); } // Adds a single wire or sequence of shards as a looped wire @@ -86,6 +86,7 @@ struct Brancher { _collectedRequirements.clear(); _copyBySerialize.clear(); + decltype(SHWire::requirements) required; SHInstanceData tmpData = data; tmpData.privateContext = nullptr; // null this, in order to create a new context! ExposedInfo shared{shared_}; diff --git a/shards/core/foundation.hpp b/shards/core/foundation.hpp index a5b572c6e74..d7e743947ea 100644 --- a/shards/core/foundation.hpp +++ b/shards/core/foundation.hpp @@ -142,6 +142,117 @@ using OwnedVar = TOwnedVar; void decRef(ShardPtr shard); void incRef(ShardPtr shard); + +template inline void arrayGrow(T &arr, size_t addlen, size_t min_cap = 4) { + // safety check to make sure this is not a borrowed foreign array! + shassert((arr.cap == 0 && arr.elements == nullptr) || (arr.cap > 0 && arr.elements != nullptr)); + + size_t min_len = arr.len + addlen; + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= arr.cap) + return; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * arr.cap) + min_cap = 2 * arr.cap; + + // realloc would be nice here but clashes with our alignment requirements, in the end this is the fastest way without a custom + // allocator + auto newbuf = new (std::align_val_t{16}) uint8_t[sizeof(arr.elements[0]) * min_cap]; + if (arr.elements) { + memcpy(newbuf, arr.elements, sizeof(arr.elements[0]) * arr.len); + ::operator delete[](arr.elements, std::align_val_t{16}); + } + arr.elements = (decltype(arr.elements))newbuf; + + // also memset to 0 new memory in order to make cloneVars valid on new items + size_t size = sizeof(arr.elements[0]) * (min_cap - arr.len); + memset(arr.elements + arr.len, 0x0, size); + + if (min_cap > UINT32_MAX) { + // this is the case for now for many reasons, but should be just fine + SHLOG_FATAL("Int array overflow, we don't support more then UINT32_MAX."); + } + arr.cap = uint32_t(min_cap); +} + +template inline void arrayPush(T &arr, const V &val) { + if ((arr.len + 1) > arr.cap) { + shards::arrayGrow(arr, 1); + } + shassert(arr.elements && "array elements cannot be null"); + arr.elements[arr.len++] = val; +} + +template inline void arrayResize(T &arr, uint32_t size) { + if (arr.cap < size) { + shards::arrayGrow(arr, size - arr.len); + } + arr.len = size; +} + +template inline void arrayShuffle(T &arr) { + // Check if the array is empty + if (arr.len == 0) + return; + + // Random number generator + static thread_local std::random_device rd; + static thread_local std::mt19937 gen(rd()); + + // Fisher-Yates shuffle + for (uint32_t i = arr.len - 1; i > 0; i--) { + std::uniform_int_distribution dis(0, i); + uint32_t j = dis(gen); + std::swap(arr.elements[i], arr.elements[j]); + } +} + +template inline void arrayInsert(T &arr, uint32_t index, const V &val) { + if ((arr.len + 1) > arr.cap) { + arrayGrow(arr, 1); + } + memmove(&arr.elements[index + 1], &arr.elements[index], sizeof(V) * (arr.len - index)); + arr.len++; + arr.elements[index] = val; +} + +template inline V arrayPop(T &arr) { + shassert(arr.len > 0); + arr.len--; + return arr.elements[arr.len]; +} + +template inline void arrayDelFast(T &arr, uint32_t index) { + shassert(arr.len > 0); + arr.len--; + // this allows eventual destroyVar/cloneVar magic + // avoiding allocations even in nested seqs + std::swap(arr.elements[index], arr.elements[arr.len]); +} + +template inline void arrayDel(T &arr, uint32_t index) { + shassert(arr.len > 0); + // this allows eventual destroyVar/cloneVar magic + // avoiding allocations even in nested seqs + arr.len--; + while (index < arr.len) { + std::swap(arr.elements[index], arr.elements[index + 1]); + index++; + } +} + +template inline void arrayFree(T &arr) { + if (arr.elements) { + ::operator delete[](arr.elements, std::align_val_t{16}); + } + memset(&arr, 0x0, sizeof(T)); +} + template struct bumping_memory_resource { char buffer[Size]; std::size_t _used = 0; @@ -220,6 +331,131 @@ struct TypeInfo { private: SHTypeInfo _info{}; }; + + +struct ExposedTypeInfo { + SHExposedTypeInfo _innerInfo{}; + ExposedTypeInfo() = default; + ExposedTypeInfo(const SHExposedTypeInfo &other) { initFrom(other); } + ExposedTypeInfo(const ExposedTypeInfo &other) { initFrom(other._innerInfo); } + ExposedTypeInfo(ExposedTypeInfo &&other) { std::swap(_innerInfo, other._innerInfo); } + ExposedTypeInfo &operator=(const SHExposedTypeInfo &other) = delete; + ExposedTypeInfo &operator=(ExposedTypeInfo &&other) { + std::swap(_innerInfo, other._innerInfo); + return *this; + } + ~ExposedTypeInfo() { clean(); } + + operator const SHExposedTypeInfo &() const { return _innerInfo; } + const SHExposedTypeInfo &operator->() const { return _innerInfo; } + const SHExposedTypeInfo &operator*() const { return _innerInfo; } + +private: + void initFrom(const SHExposedTypeInfo &other) { + _innerInfo.exposedType = cloneTypeInfo(other.exposedType); + if (other.name) { + _innerInfo.name = strdup(other.name); + } else { + _innerInfo.name = nullptr; + } + // These are typically static strings, so we can just copy the pointer + _innerInfo.help = other.help; + _innerInfo.isMutable = other.isMutable; + _innerInfo.isProtected = other.isProtected; + _innerInfo.global = other.global; + _innerInfo.tracked = other.tracked; + _innerInfo.declared = other.declared; + } + + void clean() { + if (_innerInfo.name) { + free((void *)_innerInfo.name); + _innerInfo.name = nullptr; + } + freeTypeInfo(_innerInfo.exposedType); + _innerInfo.exposedType = {}; + } +}; + +struct ExposedInfo { + /* + DO NOT USE ANYMORE, THIS IS DEPRECATED AND MESSY + IT WAS USEFUL WHEN DYNAMIC ARRAYS WERE STB ARRAYS + BUT NOW WE CAN JUST USE DESIGNATED/AGGREGATE INITIALIZERS + */ + ExposedInfo() {} + + ExposedInfo(const ExposedInfo &other) { + for (uint32_t i = 0; i < other._innerInfo.len; i++) { + push_back(other._innerInfo.elements[i]); + } + } + + ExposedInfo &operator=(const ExposedInfo &other) { + shards::arrayResize(_innerInfo, 0); + for (uint32_t i = 0; i < other._innerInfo.len; i++) { + push_back(other._innerInfo.elements[i]); + } + return *this; + } + + template explicit ExposedInfo(const ExposedInfo &other, Types... types) { + for (uint32_t i = 0; i < other._innerInfo.len; i++) { + push_back(other._innerInfo.elements[i]); + } + + std::vector vec = {types...}; + for (auto pi : vec) { + push_back(pi); + } + } + + template explicit ExposedInfo(const SHExposedTypesInfo other, Types... types) { + for (uint32_t i = 0; i < other.len; i++) { + push_back(other.elements[i]); + } + + std::vector vec = {types...}; + for (auto pi : vec) { + push_back(pi); + } + } + + template explicit ExposedInfo(const SHExposedTypeInfo &first, Types... types) { + std::vector vec = {first, types...}; + for (auto pi : vec) { + push_back(pi); + } + } + + constexpr static SHExposedTypeInfo Variable(SHString name, SHOptionalString help, SHTypeInfo type, bool isMutable = false) { + SHExposedTypeInfo res = {name, help, type, isMutable}; + return res; + } + + constexpr static SHExposedTypeInfo ProtectedVariable(SHString name, SHOptionalString help, SHTypeInfo type, + bool isMutable = false) { + SHExposedTypeInfo res = {name, help, type, isMutable, true}; + return res; + } + + constexpr static SHExposedTypeInfo GlobalVariable(SHString name, SHOptionalString help, SHTypeInfo type, + bool isMutable = false) { + SHExposedTypeInfo res = {name, help, type, isMutable, false, true}; + return res; + } + + ~ExposedInfo() { shards::arrayFree(_innerInfo); } + + void push_back(const SHExposedTypeInfo &info) { shards::arrayPush(_innerInfo, info); } + + void clear() { shards::arrayResize(_innerInfo, 0); } + + explicit operator SHExposedTypesInfo() const { return _innerInfo; } + + SHExposedTypesInfo _innerInfo{}; +}; + } // namespace shards struct SHTableImpl : public ShardsAlignedMap { @@ -325,7 +561,7 @@ struct SHWire : public std::enable_shared_from_this { // used in wires.cpp to store exposed/required types from compose operations mutable std::optional composeResult; // used sometimes in wires.cpp and .hpp when capturing variables is needed - mutable std::unordered_map requirements; + mutable std::unordered_map requirements; SHContext *context{nullptr}; @@ -345,10 +581,7 @@ struct SHWire : public std::enable_shared_from_this { size_t stackSize{SH_BASE_STACK_SIZE}; #endif - ~SHWire() { - SHLOG_TRACE("Destroying wire {}", name); - destroy(); - } + ~SHWire(); void warmup(SHContext *context); @@ -385,7 +618,7 @@ struct SHWire : public std::enable_shared_from_this { auto pref = reinterpret_cast *>(ref); // if your screen is spammed by the under, don't you dare think of removing this line... // likely a red flag and not a red herring, stop going into kernel land... - SHLOG_TRACE("{} wire deleteRef ({}) - use_count: {}", (*pref)->name, (void *)ref, pref->use_count()); + SHLOG_TRACE("{} wire deleteRef ({}) - use_count: {}", (*pref)->name, (void *)pref->get(), pref->use_count()); delete pref; } @@ -396,10 +629,11 @@ struct SHWire : public std::enable_shared_from_this { static SHWireRef addRef(SHWireRef ref) { auto cref = sharedFromRef(ref); + shassert(cref.use_count() > 0); // if your screen is spammed by the under, don't you dare think of removing this line... // likely a red flag and not a red herring, stop going into kernel land... auto res = new std::shared_ptr(cref); - SHLOG_TRACE("{} wire addRef ({}) - use_count: {}", cref->name, (void *)res, cref.use_count()); + SHLOG_TRACE("{} wire addRef ({}) - use_count: {}", cref->name, (void *)cref.get(), cref.use_count()); return reinterpret_cast(res); } @@ -836,116 +1070,6 @@ void parseArguments(int argc, const char **argv); Globals &GetGlobals(); EventDispatcher &getEventDispatcher(const std::string &name); -template inline void arrayGrow(T &arr, size_t addlen, size_t min_cap = 4) { - // safety check to make sure this is not a borrowed foreign array! - shassert((arr.cap == 0 && arr.elements == nullptr) || (arr.cap > 0 && arr.elements != nullptr)); - - size_t min_len = arr.len + addlen; - - // compute the minimum capacity needed - if (min_len > min_cap) - min_cap = min_len; - - if (min_cap <= arr.cap) - return; - - // increase needed capacity to guarantee O(1) amortized - if (min_cap < 2 * arr.cap) - min_cap = 2 * arr.cap; - - // realloc would be nice here but clashes with our alignment requirements, in the end this is the fastest way without a custom - // allocator - auto newbuf = new (std::align_val_t{16}) uint8_t[sizeof(arr.elements[0]) * min_cap]; - if (arr.elements) { - memcpy(newbuf, arr.elements, sizeof(arr.elements[0]) * arr.len); - ::operator delete[](arr.elements, std::align_val_t{16}); - } - arr.elements = (decltype(arr.elements))newbuf; - - // also memset to 0 new memory in order to make cloneVars valid on new items - size_t size = sizeof(arr.elements[0]) * (min_cap - arr.len); - memset(arr.elements + arr.len, 0x0, size); - - if (min_cap > UINT32_MAX) { - // this is the case for now for many reasons, but should be just fine - SHLOG_FATAL("Int array overflow, we don't support more then UINT32_MAX."); - } - arr.cap = uint32_t(min_cap); -} - -template inline void arrayPush(T &arr, const V &val) { - if ((arr.len + 1) > arr.cap) { - shards::arrayGrow(arr, 1); - } - shassert(arr.elements && "array elements cannot be null"); - arr.elements[arr.len++] = val; -} - -template inline void arrayResize(T &arr, uint32_t size) { - if (arr.cap < size) { - shards::arrayGrow(arr, size - arr.len); - } - arr.len = size; -} - -template inline void arrayShuffle(T &arr) { - // Check if the array is empty - if (arr.len == 0) - return; - - // Random number generator - static thread_local std::random_device rd; - static thread_local std::mt19937 gen(rd()); - - // Fisher-Yates shuffle - for (uint32_t i = arr.len - 1; i > 0; i--) { - std::uniform_int_distribution dis(0, i); - uint32_t j = dis(gen); - std::swap(arr.elements[i], arr.elements[j]); - } -} - -template inline void arrayInsert(T &arr, uint32_t index, const V &val) { - if ((arr.len + 1) > arr.cap) { - arrayGrow(arr, 1); - } - memmove(&arr.elements[index + 1], &arr.elements[index], sizeof(V) * (arr.len - index)); - arr.len++; - arr.elements[index] = val; -} - -template inline V arrayPop(T &arr) { - shassert(arr.len > 0); - arr.len--; - return arr.elements[arr.len]; -} - -template inline void arrayDelFast(T &arr, uint32_t index) { - shassert(arr.len > 0); - arr.len--; - // this allows eventual destroyVar/cloneVar magic - // avoiding allocations even in nested seqs - std::swap(arr.elements[index], arr.elements[arr.len]); -} - -template inline void arrayDel(T &arr, uint32_t index) { - shassert(arr.len > 0); - // this allows eventual destroyVar/cloneVar magic - // avoiding allocations even in nested seqs - arr.len--; - while (index < arr.len) { - std::swap(arr.elements[index], arr.elements[index + 1]); - index++; - } -} - -template inline void arrayFree(T &arr) { - if (arr.elements) { - ::operator delete[](arr.elements, std::align_val_t{16}); - } - memset(&arr, 0x0, sizeof(T)); -} - template class PtrIterator { public: typedef T value_type; @@ -1391,131 +1515,6 @@ struct ParamsInfo { SHParametersInfo _innerInfo{}; }; -struct ExposedTypeInfo { - SHExposedTypeInfo _innerInfo{}; - ExposedTypeInfo() = default; - ExposedTypeInfo(const SHExposedTypeInfo &other) { initFrom(other); } - ExposedTypeInfo(const ExposedTypeInfo &other) { - initFrom(other._innerInfo); - } - ExposedTypeInfo(ExposedTypeInfo &&other) { std::swap(_innerInfo, other._innerInfo); } - ExposedTypeInfo &operator=(const SHExposedTypeInfo &other) = delete; - ExposedTypeInfo &operator=(ExposedTypeInfo &&other) { - std::swap(_innerInfo, other._innerInfo); - return *this; - } - ~ExposedTypeInfo() { clean(); } - - operator const SHExposedTypeInfo &() const { return _innerInfo; } - const SHExposedTypeInfo &operator->() const { return _innerInfo; } - const SHExposedTypeInfo &operator*() const { return _innerInfo; } - -private: - void initFrom(const SHExposedTypeInfo &other) { - _innerInfo.exposedType = cloneTypeInfo(other.exposedType); - if (other.name) { - _innerInfo.name = strdup(other.name); - } else { - _innerInfo.name = nullptr; - } - // These are typically static strings, so we can just copy the pointer - _innerInfo.help = other.help; - _innerInfo.isMutable = other.isMutable; - _innerInfo.isProtected = other.isProtected; - _innerInfo.global = other.global; - _innerInfo.tracked = other.tracked; - _innerInfo.declared = other.declared; - } - - void clean() { - if (_innerInfo.name) { - free((void *)_innerInfo.name); - _innerInfo.name = nullptr; - } - freeTypeInfo(_innerInfo.exposedType); - _innerInfo.exposedType = {}; - } -}; - -struct ExposedInfo { - /* - DO NOT USE ANYMORE, THIS IS DEPRECATED AND MESSY - IT WAS USEFUL WHEN DYNAMIC ARRAYS WERE STB ARRAYS - BUT NOW WE CAN JUST USE DESIGNATED/AGGREGATE INITIALIZERS - */ - ExposedInfo() {} - - ExposedInfo(const ExposedInfo &other) { - for (uint32_t i = 0; i < other._innerInfo.len; i++) { - push_back(other._innerInfo.elements[i]); - } - } - - ExposedInfo &operator=(const ExposedInfo &other) { - shards::arrayResize(_innerInfo, 0); - for (uint32_t i = 0; i < other._innerInfo.len; i++) { - push_back(other._innerInfo.elements[i]); - } - return *this; - } - - template explicit ExposedInfo(const ExposedInfo &other, Types... types) { - for (uint32_t i = 0; i < other._innerInfo.len; i++) { - push_back(other._innerInfo.elements[i]); - } - - std::vector vec = {types...}; - for (auto pi : vec) { - push_back(pi); - } - } - - template explicit ExposedInfo(const SHExposedTypesInfo other, Types... types) { - for (uint32_t i = 0; i < other.len; i++) { - push_back(other.elements[i]); - } - - std::vector vec = {types...}; - for (auto pi : vec) { - push_back(pi); - } - } - - template explicit ExposedInfo(const SHExposedTypeInfo &first, Types... types) { - std::vector vec = {first, types...}; - for (auto pi : vec) { - push_back(pi); - } - } - - constexpr static SHExposedTypeInfo Variable(SHString name, SHOptionalString help, SHTypeInfo type, bool isMutable = false) { - SHExposedTypeInfo res = {name, help, type, isMutable}; - return res; - } - - constexpr static SHExposedTypeInfo ProtectedVariable(SHString name, SHOptionalString help, SHTypeInfo type, - bool isMutable = false) { - SHExposedTypeInfo res = {name, help, type, isMutable, true}; - return res; - } - - constexpr static SHExposedTypeInfo GlobalVariable(SHString name, SHOptionalString help, SHTypeInfo type, - bool isMutable = false) { - SHExposedTypeInfo res = {name, help, type, isMutable, false, true}; - return res; - } - - ~ExposedInfo() { shards::arrayFree(_innerInfo); } - - void push_back(const SHExposedTypeInfo &info) { shards::arrayPush(_innerInfo, info); } - - void clear() { shards::arrayResize(_innerInfo, 0); } - - explicit operator SHExposedTypesInfo() const { return _innerInfo; } - - SHExposedTypesInfo _innerInfo{}; -}; - using ShardsCollection = std::variant; struct ShardInfo { @@ -1714,7 +1713,7 @@ inline bool collectRequiredVariables(const SHInstanceData &data, ExposedInfo &ou std::vector expInfo; TypeInfo ti(var, data, &expInfo, false); for (auto &type : validTypes) { - if (TypeMatcher{ + if (TypeMatcher<>{ .isParameter = true, .relaxEmptyTableCheck = true, .relaxEmptySeqCheck = expInfo.empty(), .checkVarTypes = true} .match(ti, type)) { for (auto &it : expInfo) { diff --git a/shards/core/hash.inl b/shards/core/hash.inl index 18e861fde3f..8244ad807bf 100644 --- a/shards/core/hash.inl +++ b/shards/core/hash.inl @@ -63,7 +63,8 @@ template inline void HashState::updateTypeHash(const while (t.api->tableNext(t, &tit, &k, &v)) { XXH3_state_t subState{}; hashReset(&subState); - updateTypeHash(k, &subState); + // NOTE: this is by value, since we're talking about the keys + updateHash(k, &subState); updateTypeHash(v, &subState); TMP_HASH_SET.insert(hashDigest(&subState)); } @@ -127,7 +128,8 @@ template inline void HashState::updateTypeHash(const for (uint32_t i = 0; i < t.table.types.len; i++) { XXH3_state_t subState{}; hashReset(&subState); - updateTypeHash(t.table.keys.elements[i], &subState); + // NOTE: this is by value, since we're talking about the keys + updateHash(t.table.keys.elements[i], &subState); updateTypeHash(t.table.types.elements[i], &subState); TMP_HASH_SET.insert(hashDigest(&subState)); } diff --git a/shards/core/runtime.cpp b/shards/core/runtime.cpp index 976a18a2819..3c249f8d223 100644 --- a/shards/core/runtime.cpp +++ b/shards/core/runtime.cpp @@ -838,7 +838,7 @@ SHWireState activateShards2(SHSeq shards, SHContext *context, const SHVar &wireI bool matchTypes(const SHTypeInfo &inputType, const SHTypeInfo &receiverType, bool isParameter, bool strict, bool relaxEmptySeqCheck, bool ignoreFixedSeq) { - return TypeMatcher{ + return TypeMatcher<>{ .isParameter = isParameter, .strict = strict, .relaxEmptySeqCheck = relaxEmptySeqCheck, .ignoreFixedSeq = ignoreFixedSeq} .match(inputType, receiverType); } @@ -858,7 +858,7 @@ struct InternalCompositionContext { bool onWorkerThread{false}; - std::unordered_map *fullRequired{nullptr}; + std::unordered_map *fullRequired{nullptr}; InternalCompositionContext() = default; InternalCompositionContext(pmr::memory_resource *allocator) @@ -976,7 +976,7 @@ ALWAYS_INLINE void coroExtSuspend(SHWire *wire) { #ifdef SH_VERBOSE_COROUTINES_LOGGING SHLOG_TRACE("Suspending wire {}", wire->name); #endif -} +} void validateConnection(InternalCompositionContext &ctx) { ZoneScopedN("validateConnection"); @@ -1209,9 +1209,12 @@ void validateConnection(InternalCompositionContext &ctx) { matching = true; } #endif - throw ComposeError( - fmt::format("Required types do not match currently exposed ones for variable '{}' required type: (\"{}\", {})", - required.first, required.second.name, required.second.exposedType)); + if (found) { + throw ComposeError(fmt::format("Required type ({}) does not match currently exposed type ({}) for variable '{}'", + required.second.exposedType, found->exposedType, required.first)); + } else { + throw ComposeError(fmt::format("Required variable '{}' ({}) was not found", required.first, required.second.exposedType)); + } } else { // Add required stuff that we do not expose ourself if (ctx.exposed.find(match.name) == ctx.exposed.end()) @@ -1372,7 +1375,7 @@ SHComposeResult internalComposeWire(const std::vector &wire, SHInstance if (ctx.fullRequired) { for (auto &req : ctx.required) { shards::arrayPush(result.requiredInfo, req); - (*ctx.fullRequired)[req.name] = req; + (*ctx.fullRequired).insert_or_assign(req.name, ExposedTypeInfo(req)); } } else { for (auto &req : ctx.required) { @@ -2553,143 +2556,6 @@ extern "C" void shards_install_signal_handlers() { installSignalHandlers(); } // NO NAMESPACE here! -void SHWire::addTrait(SHTrait inTrait) { - auto it = std::find_if(traits.begin(), traits.end(), [&](const shards::Trait &t) { return t.sameIdAs(inTrait); }); - if (it == traits.end()) { - SHLOG_TRACE("Adding <{}> to wire \"{}\"", SHVar{.payload = {.traitValue = &inTrait}, .valueType = SHType::Trait}, name); - traits.emplace_back(inTrait); - - // Make sure to register the trait - TraitRegister::instance().insertUnique(inTrait); - } -} - -void SHWire::destroy() { - for (auto it = shards.rbegin(); it != shards.rend(); ++it) { - (*it)->cleanup(*it, nullptr); - } - for (auto it = shards.rbegin(); it != shards.rend(); ++it) { - decRef(*it); - } - - // find dangling variables, notice but do not destroy - for (auto var : variables) { - if (var.second.refcount > 0) { - SHLOG_ERROR("Found a dangling variable: {}, wire: {}", var.first, name); - } - } - - if (composeResult) { - shards::arrayFree(composeResult->requiredInfo); - shards::arrayFree(composeResult->exposedInfo); - } - - // finally reset the mesh - mesh.reset(); - -#if SH_CORO_NEED_STACK_MEM - if (stackMem) { - ::operator delete[](stackMem, std::align_val_t{16}); - } -#endif -} - -void SHWire::warmup(SHContext *context) { - if (!warmedUp) { - SHLOG_TRACE("Running warmup on wire: {}", name); - - // we likely need this early! - mesh = context->main->mesh; - warmedUp = true; - - context->wireStack.push_back(this); - DEFER({ context->wireStack.pop_back(); }); - for (auto blk : shards) { - try { - if (blk->warmup) { - auto status = blk->warmup(blk, context); - if (status.code != SH_ERROR_NONE) { - std::string_view msg(status.message.string, size_t(status.message.len)); - SHLOG_ERROR("Warmup failed on wire: {}, shard: {} (line: {}, column: {})", name, blk->name(blk), blk->line, - blk->column); - throw shards::WarmupError(msg); - } - } - if (context->failed()) { - throw shards::WarmupError(context->getErrorMessage()); - } - } catch (const std::exception &e) { - SHLOG_ERROR("Shard warmup error, failed shard: {}", blk->name(blk)); - SHLOG_ERROR(e.what()); - // if the failure is from an exception context might not be uptodate - if (!context->failed()) { - context->cancelFlow(e.what()); - } - throw; - } catch (...) { - SHLOG_ERROR("Shard warmup error, failed shard: {}", blk->name(blk)); - if (!context->failed()) { - context->cancelFlow("foreign exception failure, check logs"); - } - throw; - } - } - - SHLOG_TRACE("Ran warmup on wire: {}", name); - } else { - SHLOG_TRACE("Warmup already run on wire: {}", name); - } -} - -void SHWire::cleanup(bool force) { - if (force || (warmedUp && wireUsers.size() == 0)) { - SHLOG_TRACE("Running cleanup on wire: {} users count: {}", name, wireUsers.size()); - - warmedUp = false; - previousOutput = {}; - - dispatcher.trigger(SHWire::OnCleanupEvent{this}); - - // Run cleanup on all shards, prepare them for a new start if necessary - // Do this in reverse to allow a safer cleanup - for (auto it = shards.rbegin(); it != shards.rend(); ++it) { - auto blk = *it; - try { - blk->cleanup(blk, context); - } -#if SH_BOOST_COROUTINE - catch (boost::context::detail::forced_unwind const &e) { - SHLOG_WARNING("Shard cleanup boost forced unwind, failed shard: {}", blk->name(blk)); - throw; // required for Boost Coroutine! - } -#endif - catch (const std::exception &e) { - SHLOG_ERROR("Shard cleanup error, failed shard: {}, error: {}", blk->name(blk), e.what()); - } catch (...) { - SHLOG_ERROR("Shard cleanup error, failed shard: {}", blk->name(blk)); - } - } - - // Also clear all variables reporting dangling ones - for (auto var : variables) { - if (var.second.refcount > 0) { - SHLOG_ERROR("Found a dangling variable: {} in wire: {}", var.first, name); - } - } - variables.clear(); - - auto mesh_ = mesh.lock(); - if (mesh_) { - mesh_->unschedule(shared_from_this()); - } - mesh.reset(); - - resumer = nullptr; - - SHLOG_TRACE("Ran cleanup on wire: {}", name); - } -} - void shInit() { static bool globalInitDone = false; if (globalInitDone) diff --git a/shards/core/runtime.hpp b/shards/core/runtime.hpp index ad383bf85ea..c89e619f23c 100644 --- a/shards/core/runtime.hpp +++ b/shards/core/runtime.hpp @@ -639,11 +639,12 @@ struct SHMesh : public std::enable_shared_from_this { ++it; } - // unschedule at the end - for (const auto &item : _pendingUnschedule) { + // unschedule at the end, double buffer sets because they might be modified during iteration + std::swap(_pendingUnschedule, _pendingUnschedule1); + for (const auto &item : _pendingUnschedule1) { _scheduled.erase(item); } - _pendingUnschedule.clear(); + _pendingUnschedule1.clear(); } return noErrors; @@ -870,6 +871,7 @@ struct SHMesh : public std::enable_shared_from_this { std::unordered_set _scheduledSet; ScheduledSet _pendingSchedule; ScheduledSet _pendingUnschedule; + ScheduledSet _pendingUnschedule1; std::vector _errors; std::vector _failedWires; diff --git a/shards/core/trait.cpp b/shards/core/trait.cpp index 5f063618ccd..ceab34fee31 100644 --- a/shards/core/trait.cpp +++ b/shards/core/trait.cpp @@ -49,7 +49,7 @@ template inline auto formatLineInto(std::string & } bool TraitMatcher::operator()(SHExposedTypesInfo exposedVariables, const SHTrait &trait) { - TypeMatcher tm; + TypeMatcher<> tm; tm.ignoreFixedSeq = true; error.clear(); diff --git a/shards/core/type_matcher.hpp b/shards/core/type_matcher.hpp index 688108808ec..04f4e78482f 100644 --- a/shards/core/type_matcher.hpp +++ b/shards/core/type_matcher.hpp @@ -3,9 +3,40 @@ #include #include +#include +#include +#include +#include +#include "ops_internal.hpp" namespace shards { -struct TypeMatcher { +struct TypeMatcherErrorFormatter { + static constexpr std::true_type type_matcher_error_interface{}; + + boost::container::small_vector path; + std::vector errors; + + void formatPath(std::string &output) { + if (path.empty()) { + return; + } + + output += path[0]; + for (size_t i = 1; i < path.size(); i++) { + output += ":"; + output += path[i]; + } + } +}; + +template +concept ErrorFormatterInterface = requires(T t, std::string outStr) { + { t.errors } -> std::same_as &>; + { t.path } -> std::same_as &>; + { t.formatPath(outStr) }; +}; + +template struct TypeMatcher { // This will automatically allow extra keys in the input table // e.g. Input type {a: Int b: Float} will match against receiver type {a: Int} bool isParameter = true; @@ -15,7 +46,79 @@ struct TypeMatcher { bool checkVarTypes = false; bool ignoreFixedSeq = false; + TypeMatcherErrorFormatter errorFormatter; + + void logTypeMismatch(const SHTypeInfo &expected, const SHTypesInfo &actual) { + if constexpr (ErrorFormatterInterface) { + std::string err; + errorFormatter.formatPath(err); + if (!err.empty()) + err += ", "; + fmt::format_to(std::back_inserter(err), "type mismatch, was: {}, expected: {}", expected, actual); + errorFormatter.errors.emplace_back(std::move(err)); + } + } + + void logTypeMismatch(const SHTypeInfo &expected, const SHTypeInfo &actual) { + if constexpr (ErrorFormatterInterface) { + std::string err; + errorFormatter.formatPath(err); + if (!err.empty()) + err += ", "; + fmt::format_to(std::back_inserter(err), "type mismatch, was: {}, expected: {}", expected, actual); + errorFormatter.errors.emplace_back(std::move(err)); + } + } + + void logKeysMissing(const std::set &keys) { + if constexpr (ErrorFormatterInterface) { + std::string err; + errorFormatter.formatPath(err); + if (!err.empty()) + err += ", "; + + fmt::format_to(std::back_inserter(err), "input is missing keys: "); + size_t nk = 0; + for (auto &k : keys) { + if (nk > 0) + fmt::format_to(std::back_inserter(err), ", "); + fmt::format_to(std::back_inserter(err), "{}", k); + nk++; + } + errorFormatter.errors.emplace_back(std::move(err)); + } + } + + void appendPath(std::string_view path) { + if constexpr (ErrorFormatterInterface) { + errorFormatter.path.emplace_back(path); + } + } + void appendPath(const char *path) { + if constexpr (ErrorFormatterInterface) { + appendPath(std::string_view(path)); + } + } + void appendPath(const SHVar &v) { + if constexpr (ErrorFormatterInterface) { + appendPath(fmt::format("{}", v)); + } + } + void popPath() { + if constexpr (ErrorFormatterInterface) { + errorFormatter.path.pop_back(); + } + } + bool match(const SHTypeInfo &inputType, const SHTypeInfo &receiverType) { + if (!matchInner(inputType, receiverType)) { + logTypeMismatch(inputType, receiverType); + return false; + } + return true; + } + + bool matchInner(const SHTypeInfo &inputType, const SHTypeInfo &receiverType) { if (receiverType.basicType == SHType::Any) return true; @@ -60,6 +163,7 @@ struct TypeMatcher { match(inputType.seqTypes.elements[i], receiverType.seqTypes.elements[j])) goto matched; } + logTypeMismatch(inputType.seqTypes.elements[i], receiverType.seqTypes); return false; matched: continue; @@ -115,6 +219,8 @@ struct TypeMatcher { return true; // both Any auto matched = false; SHTypeInfo anyType{SHType::Any}; + appendPath(""); + DEFER({ popPath(); }); for (uint32_t y = 0; y < numReceiverTypes; y++) { auto btype = receiverType.table.types.elements[y]; if (match(anyType, btype)) { @@ -133,6 +239,8 @@ struct TypeMatcher { // with the consumer auto atype = inputType.table.types.elements[i]; auto matched = false; + appendPath(""); + DEFER({ popPath(); }); for (uint32_t y = 0; y < numReceiverTypes; y++) { auto btype = receiverType.table.types.elements[y]; if (match(atype, btype)) { @@ -141,6 +249,7 @@ struct TypeMatcher { } } if (!matched) { + logTypeMismatch(atype, receiverType.table.types); return false; } } @@ -159,6 +268,13 @@ struct TypeMatcher { return false; } + std::set missingReceiverKeys{}; + if constexpr (ErrorFormatterInterface) { + for (uint32_t i = 0; i < numReceiverKeys; i++) { + missingReceiverKeys.insert(receiverType.table.keys.elements[i]); + } + } + auto missingRecvMatches = lastElementEmpty ? numReceiverKeys - 1 : numReceiverKeys; for (uint32_t i = 0; i < numInputKeys; i++) { auto inputEntryType = inputType.table.types.elements[i]; @@ -168,24 +284,37 @@ struct TypeMatcher { auto receiverEntryKey = receiverType.table.keys.elements[y]; // Try to compare against the wildcard type first if (lastElementEmpty && y == (numReceiverKeys - 1)) { + appendPath(""); + DEFER({ popPath(); }); + if (match(inputEntryType, receiverEntryType)) { y = numReceiverKeys; // break - } else + } else { + logTypeMismatch(inputEntryType, receiverEntryType); return false; + } } else if (inputEntryKey == receiverEntryKey) { + appendPath(inputEntryKey); + DEFER({ popPath(); }); + if (match(inputEntryType, receiverEntryType)) { missingRecvMatches--; + if constexpr (ErrorFormatterInterface) { + missingReceiverKeys.erase(receiverEntryKey); + } y = numReceiverKeys; // break - } else + } else { + logTypeMismatch(inputEntryType, receiverEntryType); return false; - } else if (ignoreExtra) { - continue; + } } } } - if (missingRecvMatches) + if (missingRecvMatches) { + logKeysMissing(missingReceiverKeys); return false; + } } } break; diff --git a/shards/core/wires.cpp b/shards/core/wires.cpp new file mode 100644 index 00000000000..98cf513ffa5 --- /dev/null +++ b/shards/core/wires.cpp @@ -0,0 +1,150 @@ +#include "foundation.hpp" +#include "runtime.hpp" +#include "trait.hpp" + +using namespace shards; + +SHWire::~SHWire() { + SHLOG_TRACE("Destroying wire {} ({})", name, (void *)this); + if(name == "domain/draw-domain-inventory-filters") { + SHLOG_TRACE("d"); + } + destroy(); +} + +void SHWire::addTrait(SHTrait inTrait) { + auto it = std::find_if(traits.begin(), traits.end(), [&](const shards::Trait &t) { return t.sameIdAs(inTrait); }); + if (it == traits.end()) { + SHLOG_TRACE("Adding <{}> to wire \"{}\"", SHVar{.payload = {.traitValue = &inTrait}, .valueType = SHType::Trait}, name); + traits.emplace_back(inTrait); + + // Make sure to register the trait + TraitRegister::instance().insertUnique(inTrait); + } +} + +void SHWire::destroy() { + for (auto it = shards.rbegin(); it != shards.rend(); ++it) { + (*it)->cleanup(*it, nullptr); + } + for (auto it = shards.rbegin(); it != shards.rend(); ++it) { + decRef(*it); + } + + // find dangling variables, notice but do not destroy + for (auto var : variables) { + if (var.second.refcount > 0) { + SHLOG_ERROR("Found a dangling variable: {}, wire: {}", var.first, name); + } + } + + if (composeResult) { + shards::arrayFree(composeResult->requiredInfo); + shards::arrayFree(composeResult->exposedInfo); + } + + // finally reset the mesh + mesh.reset(); + +#if SH_CORO_NEED_STACK_MEM + if (stackMem) { + ::operator delete[](stackMem, std::align_val_t{16}); + } +#endif +} + +void SHWire::warmup(SHContext *context) { + if (!warmedUp) { + SHLOG_TRACE("Running warmup on wire: {}", name); + + // we likely need this early! + mesh = context->main->mesh; + warmedUp = true; + + context->wireStack.push_back(this); + DEFER({ context->wireStack.pop_back(); }); + for (auto blk : shards) { + try { + if (blk->warmup) { + auto status = blk->warmup(blk, context); + if (status.code != SH_ERROR_NONE) { + std::string_view msg(status.message.string, size_t(status.message.len)); + SHLOG_ERROR("Warmup failed on wire: {}, shard: {} (line: {}, column: {})", name, blk->name(blk), blk->line, + blk->column); + throw shards::WarmupError(msg); + } + } + if (context->failed()) { + throw shards::WarmupError(context->getErrorMessage()); + } + } catch (const std::exception &e) { + SHLOG_ERROR("Shard warmup error, failed shard: {}", blk->name(blk)); + SHLOG_ERROR(e.what()); + // if the failure is from an exception context might not be uptodate + if (!context->failed()) { + context->cancelFlow(e.what()); + } + throw; + } catch (...) { + SHLOG_ERROR("Shard warmup error, failed shard: {}", blk->name(blk)); + if (!context->failed()) { + context->cancelFlow("foreign exception failure, check logs"); + } + throw; + } + } + + SHLOG_TRACE("Ran warmup on wire: {}", name); + } else { + SHLOG_TRACE("Warmup already run on wire: {}", name); + } +} + +void SHWire::cleanup(bool force) { + if (force || (warmedUp && wireUsers.size() == 0)) { + SHLOG_TRACE("Running cleanup on wire: {} users count: {}", name, wireUsers.size()); + + warmedUp = false; + previousOutput = {}; + + dispatcher.trigger(SHWire::OnCleanupEvent{this}); + + // Run cleanup on all shards, prepare them for a new start if necessary + // Do this in reverse to allow a safer cleanup + for (auto it = shards.rbegin(); it != shards.rend(); ++it) { + auto blk = *it; + try { + blk->cleanup(blk, context); + } +#if SH_BOOST_COROUTINE + catch (boost::context::detail::forced_unwind const &e) { + SHLOG_WARNING("Shard cleanup boost forced unwind, failed shard: {}", blk->name(blk)); + throw; // required for Boost Coroutine! + } +#endif + catch (const std::exception &e) { + SHLOG_ERROR("Shard cleanup error, failed shard: {}, error: {}", blk->name(blk), e.what()); + } catch (...) { + SHLOG_ERROR("Shard cleanup error, failed shard: {}", blk->name(blk)); + } + } + + // Also clear all variables reporting dangling ones + for (auto var : variables) { + if (var.second.refcount > 0) { + SHLOG_ERROR("Found a dangling variable: {} in wire: {}", var.first, name); + } + } + variables.clear(); + + auto mesh_ = mesh.lock(); + if (mesh_) { + mesh_->unschedule(shared_from_this()); + } + mesh.reset(); + + resumer = nullptr; + + SHLOG_TRACE("Ran cleanup on wire: {}", name); + } +} diff --git a/shards/egui/src/visual/mod.rs b/shards/egui/src/visual/mod.rs index b3d876727c0..618e819cc8d 100644 --- a/shards/egui/src/visual/mod.rs +++ b/shards/egui/src/visual/mod.rs @@ -5,7 +5,7 @@ use directory::{get_global_map, get_global_name_btree}; use egui::*; use nanoid::nanoid; -use std::cell::RefCell; +use std::{borrow::Cow, cell::RefCell}; use std::sync::mpsc; use crate::{ @@ -27,12 +27,7 @@ use shards::{ }; use shards_lang::{ - ast::*, - ast_visitor::*, - custom_state::*, - directory, - read::{AST_TYPE, AST_VAR_TYPE}, - ParamHelperMut, RcStrWrapper, + ast::*, ast_visitor::*, custom_state::*, directory, read::{AST_TYPE, AST_VAR_TYPE}, ParamHelperMut, RcBytesWrapper, RcStrWrapper }; use num_traits::{Float, FromPrimitive, PrimInt, Zero}; @@ -120,7 +115,7 @@ fn var_to_value(var: &Var) -> Result { var.payload.__bindgen_anon_1.__bindgen_anon_4.bytesSize as usize, ) }; - Ok(Value::Bytes(bytes.into())) + Ok(Value::Bytes(RcBytesWrapper::from(Cow::Owned(bytes.into())))) } SHType_Float2 => { let float2 = unsafe { var.payload.__bindgen_anon_1.float2Value }; @@ -2398,7 +2393,7 @@ fn transform_take_table(x: &mut Identifier, y: &mut Vec) -> Sequen let mut blocks = vec![Block { content: BlockContent::Shard(Function { name: Identifier { - name: "Get".into(), + name: RcStrWrapper::from_const("Get"), namespaces: Vec::new(), custom_state: CustomStateContainer::new(), }, @@ -2418,7 +2413,7 @@ fn transform_take_table(x: &mut Identifier, y: &mut Vec) -> Sequen blocks.push(Block { content: BlockContent::Shard(Function { name: Identifier { - name: "Take".into(), + name: RcStrWrapper::from_const("Take"), namespaces: Vec::new(), custom_state: CustomStateContainer::new(), }, @@ -2447,7 +2442,7 @@ fn transform_take_seq(x: &mut Identifier, y: &mut Vec) -> Sequence { let mut blocks = vec![Block { content: BlockContent::Shard(Function { name: Identifier { - name: "Get".into(), + name: RcStrWrapper::from_const("Get"), namespaces: Vec::new(), custom_state: CustomStateContainer::new(), }, @@ -2467,7 +2462,7 @@ fn transform_take_seq(x: &mut Identifier, y: &mut Vec) -> Sequence { blocks.push(Block { content: BlockContent::Shard(Function { name: Identifier { - name: "Take".into(), + name: RcStrWrapper::from_const("Take"), namespaces: Vec::new(), custom_state: CustomStateContainer::new(), }, diff --git a/shards/gfx/gizmos/wireframe.cpp b/shards/gfx/gizmos/wireframe.cpp index 28e99826202..cb641398032 100644 --- a/shards/gfx/gizmos/wireframe.cpp +++ b/shards/gfx/gizmos/wireframe.cpp @@ -54,7 +54,7 @@ MeshPtr WireframeMeshGenerator::generate() { size_t srcAttribIdx = attributesToCopy[i]; outAttributes.push_back({&attributes[i], srcFormat.vertexAttributes[srcAttribIdx].name}); } - return generateMesh(std::nullopt, boost::span(outAttributes)); + return generateMesh(std::nullopt, boost::span(outAttributes), ""); } WireframeRenderer::WireframeRenderer(bool showBackfaces) { diff --git a/shards/gfx/mesh_utils.cpp b/shards/gfx/mesh_utils.cpp index 459ce0998f1..4dc2faf3786 100644 --- a/shards/gfx/mesh_utils.cpp +++ b/shards/gfx/mesh_utils.cpp @@ -159,6 +159,6 @@ MeshPtr generateLocalBasisAttribute(MeshPtr mesh) { outAttribs.push_back({&srcAttributes[i], srcFormat.vertexAttributes[i].name}); } outAttribs.push_back({&tangentBuffer, "tangent"}); - return generateMesh(std::nullopt, outAttribs); + return generateMesh(std::nullopt, outAttribs, ""); } } // namespace gfx \ No newline at end of file diff --git a/shards/gfx/mesh_utils.hpp b/shards/gfx/mesh_utils.hpp index 6963540923a..6a46070bfc4 100644 --- a/shards/gfx/mesh_utils.hpp +++ b/shards/gfx/mesh_utils.hpp @@ -211,7 +211,7 @@ struct AttribBuffer { // Reassembles a mesh out of a set of attributes and an optional index buffer inline MeshPtr generateMesh(std::optional indexBuffer, - boost::span> attributes) { + boost::span> attributes, const char* generatedName = "") { MeshPtr newMesh = std::make_shared(); MeshFormat format; format.primitiveType = PrimitiveType::TriangleList; @@ -249,7 +249,7 @@ inline MeshPtr generateMesh(std::optional indexBuffer, newMesh->update(format, std::move(vertexBuffer)); } #if SH_GFX_CONTEXT_DATA_LABELS - newMesh->setLabel(fmt::format("wireframe")); + newMesh->setLabel(generatedName); #endif return newMesh; diff --git a/shards/lang/src/ast.rs b/shards/lang/src/ast.rs index 4616f0844d1..9ea21674237 100644 --- a/shards/lang/src/ast.rs +++ b/shards/lang/src/ast.rs @@ -7,7 +7,7 @@ use serde::{ser::SerializeStruct, Deserialize, Serialize}; use shards::{ types::Var, SHType_Bool, SHType_Bytes, SHType_Float, SHType_Int, SHType_None, SHType_String, }; -use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hasher}; +use std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt::Debug, hash::Hasher}; #[derive(Parser)] #[grammar = "shards.pest"] @@ -237,11 +237,11 @@ impl TryFrom for Value { ))), SHType_String => { let s: &str = value.as_ref().try_into().unwrap(); - Ok(Value::String(s.into())) + Ok(Value::String(RcStrWrapper::from(Cow::Owned(s.into())))) } SHType_Bytes => { let b: &[u8] = value.as_ref().try_into().unwrap(); - Ok(Value::Bytes(b.into())) + Ok(Value::Bytes(RcBytesWrapper::from(Cow::Owned(b.into())))) } _ => Err("Unsupported type"), } diff --git a/shards/lang/src/eval.rs b/shards/lang/src/eval.rs index f8ef44f91fc..829e3260fda 100644 --- a/shards/lang/src/eval.rs +++ b/shards/lang/src/eval.rs @@ -7,6 +7,7 @@ use crate::RcStrWrapper; use crate::ShardsExtension; use core::convert::TryInto; +use std::borrow::Cow; use nanoid::nanoid; use shards::fourCharacterCode; @@ -191,8 +192,8 @@ impl EvalEnv { let mut env = EvalEnv { program, parent: None, - namespace: RcStrWrapper::from(""), - full_namespace: RcStrWrapper::from(""), + namespace: RcStrWrapper::from_const(""), + full_namespace: RcStrWrapper::from_const(""), qualified_cache: HashMap::new(), shards: Vec::new(), deferred_wires: HashMap::new(), @@ -2042,6 +2043,11 @@ impl<'e> VariableResolver<'e> { let ctx: ClonedVar = capture_eval_context(self.e); Ok(ResolvedVar::new_const(SVar::Cloned(ctx))) } + ("namespace", true) => { + let namespace = self.e.full_namespace.clone(); + let namespace = namespace.as_str(); + Ok(ResolvedVar::new_const(SVar::Cloned(ClonedVar::from(Var::ephemeral_string(namespace))))) + } _ => { if let Some(defined_value) = find_defined(&func.name, self.e).map(|x| x.clone()) { match defined_value { @@ -3406,7 +3412,7 @@ fn eval_pipeline( blocks: vec![Block { content: BlockContent::Shard(Function { name: Identifier { - name: "_MakeTrait".into(), + name: RcStrWrapper::from_const("_MakeTrait"), namespaces: vec![], custom_state: CustomStateContainer::new(), }, @@ -4511,7 +4517,7 @@ impl Shard for EvalShard { let mut env = if namespace.is_string() { let namespace: &str = namespace.try_into()?; EvalEnv::new( - Some(namespace.into()), + Some(RcStrWrapper::from(Cow::Owned(namespace.into()))), parent_ptr, Some(prog as *const Program), ) @@ -4532,7 +4538,7 @@ impl Shard for EvalShard { for (k, v) in &defines_storage { env.definitions.insert( Identifier { - name: (*k).into(), + name: RcStrWrapper::new(Cow::Owned((*k).into())), namespaces: Vec::new(), custom_state: CustomStateContainer::new(), }, @@ -4545,7 +4551,7 @@ impl Shard for EvalShard { for shard in seq.iter() { let shard_name: &str = shard.as_ref().try_into()?; env.forbidden_funcs.insert(Identifier { - name: RcStrWrapper::from(shard_name), + name: RcStrWrapper::from(Cow::Owned(shard_name.into())), namespaces: Vec::new(), custom_state: CustomStateContainer::new(), }); diff --git a/shards/lang/src/lib.rs b/shards/lang/src/lib.rs index 5ac6f820721..5eca3153064 100644 --- a/shards/lang/src/lib.rs +++ b/shards/lang/src/lib.rs @@ -105,9 +105,9 @@ impl RcBytesWrapper { } } -impl From<&'static [u8]> for RcBytesWrapper { - fn from(s: &'static [u8]) -> Self { - RcBytesWrapper::new(Cow::Borrowed(s)) +impl From> for RcBytesWrapper { + fn from(s: Cow<'static, [u8]>) -> Self { + RcBytesWrapper::new(s) } } @@ -177,6 +177,10 @@ impl RcStrWrapper { RcStrWrapper(Rc::new(s.into())) } + pub fn from_const(s: &'static str) -> Self { + RcStrWrapper(Rc::new(Cow::Borrowed(s))) + } + pub fn to_string(&self) -> String { self.0.to_string() } @@ -191,9 +195,9 @@ impl RcStrWrapper { } } -impl From<&'static str> for RcStrWrapper { - fn from(s: &'static str) -> Self { - RcStrWrapper::new(Cow::Borrowed(s)) +impl<'a> RcStrWrapper { + pub fn new_clone(s: &'a str) -> Self { + RcStrWrapper::new(Cow::Owned(s.to_string())) } } @@ -203,6 +207,18 @@ impl From for RcStrWrapper { } } +impl From> for RcStrWrapper { + fn from(s: Cow<'static, str>) -> Self { + RcStrWrapper::new(s) + } +} + +impl From<&'static str> for RcStrWrapper { + fn from(s: &'static str) -> Self { + RcStrWrapper::from_const(s) + } +} + impl Eq for RcStrWrapper {} impl PartialEq for RcStrWrapper { diff --git a/shards/lang/src/read.rs b/shards/lang/src/read.rs index bf400aae071..58339d1c866 100644 --- a/shards/lang/src/read.rs +++ b/shards/lang/src/read.rs @@ -281,7 +281,7 @@ fn substitute_inline_template<'a>( pos: Position<'a>, ) -> Result { // Foreach param, match against InlineTemplate and substitute value in string - let templ_args = unsafe { &*itempl.args }; + let templ_args = &*itempl.args; let mut str = itempl.shards.clone(); let mut i = 0; loop { @@ -318,7 +318,7 @@ fn convert_to_function_value<'a>( let itc: Option = env.find_inline_template(&identifier).cloned(); if let Some(itempl) = itc { let itempl = itempl.clone(); - let mut prog: Program = + let prog: Program = env.with_inline_template_scope(ReadEnvType::InlineTemplateSubstitution, |env| { let params = params.ok_or_else(|| ("Expected parameters", pos).into())?; diff --git a/shards/modules/core/casting.cpp b/shards/modules/core/casting.cpp index 89de24d0460..66ce06c9efa 100644 --- a/shards/modules/core/casting.cpp +++ b/shards/modules/core/casting.cpp @@ -460,7 +460,19 @@ static inline void expectTypeCheck(const SHVar &input, uint64_t expectedTypeHash it = typeCache.emplace(inputTypeHash, TypeInfo{input, SHInstanceData{}}).first; } if (!matchTypes(it->second, expectedType, true, true, true)) { - throw ActivationError(fmt::format("Unexpected value: {} expected type: {}", input, expectedType)); + TypeMatcher tm{ + .isParameter = true, + .strict = true, + .relaxEmptySeqCheck = true, + .ignoreFixedSeq = false, + }; + tm.match(it->second, expectedType); + if (tm.errorFormatter.errors.empty()) { + throw ActivationError(fmt::format("Unexpected value: {} expected type: {}", input, expectedType)); + } else { + const std::string &err = tm.errorFormatter.errors.front(); + throw ActivationError(fmt::format("Unexpected value: {} expected type: {}\n{}", input, expectedType, err)); + } } } } @@ -1274,13 +1286,11 @@ struct AudioToFloats { static SHOptionalString inputHelp() { return SHCCSTR("Takes an audio buffer as input."); } static SHTypesInfo outputTypes() { return _outputType; } - static SHOptionalString outputHelp() { - return SHCCSTR("Outputs the input audio represented as a sequence of float values."); - } + static SHOptionalString outputHelp() { return SHCCSTR("Outputs the input audio represented as a sequence of float values."); } static SHOptionalString help() { return SHCCSTR("Converts an audio buffer into a sequence of float values. Each sample in the audio buffer " - "is converted to a float value in the range [-1.0, 1.0] and stored in the sequence."); + "is converted to a float value in the range [-1.0, 1.0] and stored in the sequence."); } std::vector _output; @@ -1321,9 +1331,7 @@ struct FloatsToAudio { static inline Type _inputType{{SHType::Seq, {.seqTypes = _inputElemType}}}; static SHTypesInfo inputTypes() { return _inputType; } - static SHOptionalString inputHelp() { - return SHCCSTR("Takes a sequence of float values in the range [-1.0, 1.0] as input."); - } + static SHOptionalString inputHelp() { return SHCCSTR("Takes a sequence of float values in the range [-1.0, 1.0] as input."); } static SHTypesInfo outputTypes() { return CoreInfo::AudioType; } static SHOptionalString outputHelp() { return SHCCSTR("Returns the constructed audio buffer."); } @@ -1337,7 +1345,7 @@ struct FloatsToAudio { std::vector _samples; FloatsToAudio() { - _channels = Var(1); // Default to mono + _channels = Var(1); // Default to mono _sampleRate = Var(44100); // Default to CD quality } @@ -1352,14 +1360,14 @@ struct FloatsToAudio { throw ActivationError("Number of channels must be greater than zero."); uint32_t totalSamples = input.payload.seqValue.len; - + // Check that the sample count is divisible by the number of channels if (totalSamples % channels != 0) { throw ActivationError("Sequence length must be divisible by the number of channels."); } uint32_t nsamples = totalSamples / channels; - + if (nsamples > UINT16_MAX) { throw ActivationError("Audio data exceeds the maximum number of samples (65535)"); } @@ -1368,16 +1376,16 @@ struct FloatsToAudio { // Copy the float values from the sequence to the samples array for (uint32_t i = 0; i < totalSamples; i++) { - const auto& elem = input.payload.seqValue.elements[i]; + const auto &elem = input.payload.seqValue.elements[i]; if (elem.valueType != SHType::Float) { throw ActivationError("All elements in the sequence must be of type Float."); } - + float value = elem.payload.floatValue; - + // Clamp values to [-1.0, 1.0] range value = std::min(std::max(value, -1.0f), 1.0f); - + _samples[i] = value; } @@ -1664,5 +1672,8 @@ SHARDS_REGISTER_FN(casting) { REGISTER_SHARD("HexToBytes", HexToBytes); REGISTER_SHARD("VarPtr!", VarPtr); + + static_assert(shards::ErrorFormatterInterface, + "TypeMatcherErrorFormatter must implement ErrorFormatterInterface"); } }; // namespace shards diff --git a/shards/modules/core/wires.cpp b/shards/modules/core/wires.cpp index f91701792ed..a8b533ba5a3 100644 --- a/shards/modules/core/wires.cpp +++ b/shards/modules/core/wires.cpp @@ -44,7 +44,7 @@ std::unordered_set &WireBase::gatheringWires() { #endif } -void WireBase::verifyAlreadyComposed(const SHInstanceData &data, IterableExposedInfo &shared) { +void WireBase::verifyAlreadyComposed(const SHInstanceData &data, const IterableExposedInfo &shared) { SHLOG_TRACE("WireBase::verifyAlreadyComposed, source: {} composing: {} inputType: {}", data.wire ? data.wire->name : nullptr, wire->name, data.inputType); // verify input type @@ -58,11 +58,11 @@ void WireBase::verifyAlreadyComposed(const SHInstanceData &data, IterableExposed for (auto &req : wire->composeResult->requiredInfo) { // find each in shared auto name = req.name; - auto res = std::find_if(shared.begin(), shared.end(), [name](SHExposedTypeInfo &x) { + auto res = std::find_if(shared.cbegin(), shared.cend(), [name](const SHExposedTypeInfo &x) { std::string_view xNameView(x.name); return name == xNameView; }); - if (res == shared.end()) { + if (res == shared.cend()) { throw ComposeError( fmt::format("Attempted to call an already composed wire ({}) with a missing required variable: {}", wire->name, name)); } @@ -156,21 +156,25 @@ SHTypeInfo WireBase::compose(const SHInstanceData &data) { auto dataCopy = data; dataCopy.wire = wire.get(); - IterableExposedInfo shared(data.shared); - IterableExposedInfo sharedCopy; + ExposedInfo sharedCopy; if (!wire->pure) { if (mode == RunWireMode::Async && !capturing) { // keep only globals - auto end = std::remove_if(shared.begin(), shared.end(), [](const SHExposedTypeInfo &x) { return !x.global; }); - sharedCopy = IterableExposedInfo(shared.begin(), end); + for (auto &x : IterableExposedInfo(data.shared)) { + if(x.global) { + sharedCopy.push_back(x); + } + } + dataCopy.shared = SHExposedTypesInfo(sharedCopy); } else { // we allow Detached but they need to be referenced during warmup - sharedCopy = shared; + sharedCopy = ExposedInfo(data.shared); + dataCopy.shared = data.shared; } + } else { + dataCopy.shared = {}; } - dataCopy.shared = sharedCopy; - // make sure to compose only once... if (!wire->composeResult) { SHLOG_TRACE("Running {} compose, pure: {}", wire->name, wire->pure); @@ -192,7 +196,7 @@ SHTypeInfo WireBase::compose(const SHInstanceData &data) { } else { SHLOG_TRACE("Skipping {} compose", wire->name); - verifyAlreadyComposed(data, shared); + verifyAlreadyComposed(data, IterableExposedInfo(data.shared)); } // write output type diff --git a/shards/modules/core/wires.hpp b/shards/modules/core/wires.hpp index 62cabdd8bc9..7ac54cf1018 100644 --- a/shards/modules/core/wires.hpp +++ b/shards/modules/core/wires.hpp @@ -64,7 +64,7 @@ struct WireBase { std::unordered_set &gatheringWires(); - void verifyAlreadyComposed(const SHInstanceData &data, IterableExposedInfo &shared); + void verifyAlreadyComposed(const SHInstanceData &data, const IterableExposedInfo &shared); SHTypeInfo compose(const SHInstanceData &data); diff --git a/shards/modules/inputs/detached.cpp b/shards/modules/inputs/detached.cpp index 086022fffd3..be8f51aa08d 100644 --- a/shards/modules/inputs/detached.cpp +++ b/shards/modules/inputs/detached.cpp @@ -331,11 +331,11 @@ struct Detached { auto mainInstanceData = data; mainInstanceData.inputType = mainDataSeqType; + auto mainCr = _mainShards.compose(mainInstanceData); for (auto req : mainCr.requiredInfo) { _requiredVariables.push_back(req); } - return mainDataSeqType; } diff --git a/shards/modules/langffi/src/lib.rs b/shards/modules/langffi/src/lib.rs index 83e146791f4..3b38937d2e5 100644 --- a/shards/modules/langffi/src/lib.rs +++ b/shards/modules/langffi/src/lib.rs @@ -48,9 +48,9 @@ pub extern "C" fn shards_read( out_ast: *mut SHLAst, ) -> bool { profiling::scope!("shards_read"); - let name: &str = name.into(); - let code = code.into(); - let base_path: &str = base_path.into(); + let name: &str = name.str(); + let code = code.str(); + let base_path: &str = base_path.str(); let include_dirs = unsafe { if num_include_dirs > 0 { std::slice::from_raw_parts(include_dirs, num_include_dirs as usize) @@ -61,7 +61,7 @@ pub extern "C" fn shards_read( let include_dirs: Vec = include_dirs .iter() .map(|x| { - let str: &str = (*x).into(); + let str: &str = (*x).str(); str.to_string() }) .collect(); @@ -149,8 +149,8 @@ pub extern "C" fn shards_create_env(namespace: SHStringWithLen) -> *mut EvalEnv if namespace.len == 0 { Box::into_raw(Box::new(EvalEnv::new(None, None, None))) } else { - let namespace: &str = namespace.into(); - Box::into_raw(Box::new(EvalEnv::new(Some(namespace.into()), None, None))) + let namespace: &str = namespace.str(); + Box::into_raw(Box::new(EvalEnv::new(Some(RcStrWrapper::new_clone(namespace)), None, None))) } } @@ -158,9 +158,8 @@ pub extern "C" fn shards_create_env(namespace: SHStringWithLen) -> *mut EvalEnv pub extern "C" fn shards_forbid_shard(env: *mut EvalEnv, name: SHStringWithLen) { profiling::scope!("shards_forbid_shard"); let env = unsafe { &mut *env }; - let name: &str = name.into(); env.forbidden_funcs.insert(Identifier { - name: RcStrWrapper::from(name), + name: RcStrWrapper::new_clone(name.str()), namespaces: Vec::new(), custom_state: CustomStateContainer::new(), }); @@ -183,9 +182,9 @@ pub extern "C" fn shards_create_sub_env( if namespace.len == 0 { Box::into_raw(Box::new(EvalEnv::new(None, Some(env), None))) } else { - let namespace: &str = namespace.into(); + let namespace: &str = namespace.str(); Box::into_raw(Box::new(EvalEnv::new( - Some(namespace.into()), + Some(RcStrWrapper::new_clone(namespace)), Some(env), None, ))) @@ -225,7 +224,7 @@ pub extern "C" fn shards_transform_env( out_wire: *mut SHLWire, ) -> bool { profiling::scope!("shards_transform_env"); - let name = name.into(); + let name = name.str(); let mut env = unsafe { Box::from_raw(env) }; let res = eval::transform_env(&mut env, name); match res { @@ -260,7 +259,7 @@ pub extern "C" fn shards_transform_envs( out_wire: *mut SHLWire, ) -> bool { profiling::scope!("shards_transform_envs"); - let name = name.into(); + let name = name.str(); let envs = unsafe { std::slice::from_raw_parts_mut(env, len) }; let mut deref_envs = Vec::with_capacity(len); for &env in envs.iter() { @@ -299,7 +298,7 @@ pub extern "C" fn shards_eval_ast( out_wire: *mut SHLWire, ) -> bool { profiling::scope!("shards_eval_ast"); - let name = name.into(); + let name = name.str(); // we just want a reference to the sequence, not ownership let ast = unsafe { &mut *Var::from_ref_counted_object::(ast, &AST_TYPE).expect("A valid AST variable.") diff --git a/shards/modules/network/src/ws_api.rs b/shards/modules/network/src/ws_api.rs index b7f9f6cfd9e..d0f742e62af 100644 --- a/shards/modules/network/src/ws_api.rs +++ b/shards/modules/network/src/ws_api.rs @@ -9,10 +9,6 @@ use log::{error, info}; const VERSION_STR: &str = concat!(env!("CARGO_PKG_VERSION"), "\0"); -fn c_str_to_string(s: SHStringWithLen) -> &'static str { - s.into() -} - #[no_mangle] pub extern "C" fn pollnet_init() -> *mut PollnetContext { Box::into_raw(Box::new(PollnetContext::new())) @@ -50,8 +46,7 @@ pub unsafe extern "C" fn pollnet_shutdown(ctx: *mut PollnetContext) { #[no_mangle] pub unsafe extern "C" fn pollnet_open_ws(ctx: *mut PollnetContext, url: SHStringWithLen) -> u64 { let ctx: &mut PollnetContext = unsafe { &mut *ctx }; - let url = c_str_to_string(url); - ctx.open_ws(url).into() + ctx.open_ws(url.str()).into() } /// # Safety @@ -64,7 +59,7 @@ pub unsafe extern "C" fn pollnet_open_ws_with_headers( headers: &TableVar, ) -> u64 { let ctx: &mut PollnetContext = unsafe { &mut *ctx }; - let url = c_str_to_string(url); + let url = url.str(); ctx.open_ws_with_headers(url, headers).into() } @@ -74,7 +69,7 @@ pub unsafe extern "C" fn pollnet_open_ws_with_headers( #[no_mangle] pub unsafe extern "C" fn pollnet_listen_ws(ctx: *mut PollnetContext, addr: SHStringWithLen) -> u64 { let ctx = unsafe { &mut *ctx }; - let addr = c_str_to_string(addr); + let addr = addr.str(); ctx.listen_ws(addr).into() } @@ -131,7 +126,7 @@ pub unsafe extern "C" fn pollnet_send_binary( #[no_mangle] pub unsafe extern "C" fn pollnet_send(ctx: *mut PollnetContext, handle: u64, msg: SHStringWithLen) { let ctx = unsafe { &mut *ctx }; - let msg = c_str_to_string(msg); + let msg = msg.str(); ctx.send(handle.into(), msg.to_string()) } diff --git a/shards/rust/src/types.rs b/shards/rust/src/types.rs index 7e2e5085e63..a1003f57f21 100644 --- a/shards/rust/src/types.rs +++ b/shards/rust/src/types.rs @@ -864,7 +864,7 @@ impl ShardRef { } if err.code != 0 { - Err(err.message.into()) + Err(err.message.static_str()) } else { Ok(()) } @@ -1196,20 +1196,22 @@ impl From for ClonedVar { } } -impl From for &str { - fn from(s: SHStringWithLen) -> Self { - unsafe { - if s.len == 0 { - return ""; - } - let slice = slice::from_raw_parts(s.string as *const u8, s.len as usize); - let s = std::str::from_utf8(slice); - match s { - Ok(s) => s, - Err(e) => { - let valid = e.valid_up_to(); - std::str::from_utf8(&slice[..valid]).unwrap() - } +impl<'a> SHStringWithLen { + pub fn str(&'a self) -> &'a str { + unsafe { self.static_str() } + } + + pub unsafe fn static_str(&self) -> &'static str { + if self.len == 0 { + return ""; + } + let slice = slice::from_raw_parts(self.string as *const u8, self.len as usize); + let s = std::str::from_utf8(slice); + match s { + Ok(s) => s, + Err(e) => { + let valid = e.valid_up_to(); + std::str::from_utf8(&slice[..valid]).unwrap() } } } diff --git a/shards/tests/server/control.shs b/shards/tests/server/control.shs index 3a03e503995..df9d10144e6 100644 --- a/shards/tests/server/control.shs +++ b/shards/tests/server/control.shs @@ -13,7 +13,7 @@ ToJson | Http.Put(URL: control-url Headers: hdrs) } "shutdown" { - {action: @action} + {action: @action data: ""} ToJson | Http.Put(URL: control-url Headers: hdrs) } ; Command to preload a given folder, which is required for it to be available in the file system