#include "preview/RuntimePlugin.hpp" #include #include #if defined(_WIN32) #define NOMINMAX #include #else #include #endif namespace dts { namespace { void* openLibrary(const std::filesystem::path& path, std::string& error) { #if defined(_WIN32) auto* handle = LoadLibraryW(path.wstring().c_str()); if (!handle) { error = "LoadLibraryW failed: " + std::to_string(GetLastError()); } return handle; #else auto* handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); if (!handle) { error = dlerror(); } return handle; #endif } void closeLibrary(void* handle) { #if defined(_WIN32) FreeLibrary(static_cast(handle)); #else dlclose(handle); #endif } void* findSymbol(void* handle, const char* name) { #if defined(_WIN32) return reinterpret_cast(GetProcAddress(static_cast(handle), name)); #else return dlsym(handle, name); #endif } template Function functionPointer(void* symbol) { Function function = nullptr; static_assert(sizeof(function) == sizeof(symbol)); std::memcpy(&function, &symbol, sizeof(function)); return function; } std::string utf8Path(const std::filesystem::path& path) { const auto value = path.u8string(); return {reinterpret_cast(value.data()), value.size()}; } } // namespace RuntimePlugin::~RuntimePlugin() { unload(); } RuntimePlugin::RuntimePlugin(RuntimePlugin&& other) noexcept { *this = std::move(other); } RuntimePlugin& RuntimePlugin::operator=(RuntimePlugin&& other) noexcept { if (this != &other) { unload(); handle_ = std::exchange(other.handle_, nullptr); getApiVersion_ = std::exchange(other.getApiVersion_, nullptr); getInfo_ = std::exchange(other.getInfo_, nullptr); create_ = std::exchange(other.create_, nullptr); destroy_ = std::exchange(other.destroy_, nullptr); load_ = std::exchange(other.load_, nullptr); getSkeletonInfo_ = std::exchange(other.getSkeletonInfo_, nullptr); getAnimationName_ = std::exchange(other.getAnimationName_, nullptr); getAnimationDuration_ = std::exchange(other.getAnimationDuration_, nullptr); getSkinName_ = std::exchange(other.getSkinName_, nullptr); getLastError_ = std::exchange(other.getLastError_, nullptr); setAnimation_ = std::exchange(other.setAnimation_, nullptr); update_ = std::exchange(other.update_, nullptr); getDrawCommandCount_ = std::exchange(other.getDrawCommandCount_, nullptr); getDrawCommand_ = std::exchange(other.getDrawCommand_, nullptr); context_ = std::exchange(other.context_, nullptr); } return *this; } bool RuntimePlugin::load(const std::filesystem::path& path, std::string& error) { unload(); handle_ = openLibrary(path, error); if (!handle_) { return false; } getApiVersion_ = functionPointer(findSymbol(handle_, "dtsRuntimeGetApiVersion")); getInfo_ = functionPointer(findSymbol(handle_, "dtsRuntimeGetInfo")); if (!getApiVersion_ || !getInfo_) { error = "Runtime plugin does not expose the required DateToSpine ABI."; unload(); return false; } if (getApiVersion_() != DTS_RUNTIME_API_VERSION) { error = "Runtime plugin API version is incompatible."; unload(); return false; } create_ = functionPointer(findSymbol(handle_, "dtsRuntimeCreate")); destroy_ = functionPointer(findSymbol(handle_, "dtsRuntimeDestroy")); load_ = functionPointer(findSymbol(handle_, "dtsRuntimeLoad")); getSkeletonInfo_ = functionPointer( findSymbol(handle_, "dtsRuntimeGetSkeletonInfo")); getAnimationName_ = functionPointer( findSymbol(handle_, "dtsRuntimeGetAnimationName")); getAnimationDuration_ = functionPointer( findSymbol(handle_, "dtsRuntimeGetAnimationDuration")); getSkinName_ = functionPointer(findSymbol(handle_, "dtsRuntimeGetSkinName")); getLastError_ = functionPointer(findSymbol(handle_, "dtsRuntimeGetLastError")); setAnimation_ = functionPointer(findSymbol(handle_, "dtsRuntimeSetAnimation")); update_ = functionPointer(findSymbol(handle_, "dtsRuntimeUpdate")); getDrawCommandCount_ = functionPointer( findSymbol(handle_, "dtsRuntimeGetDrawCommandCount")); getDrawCommand_ = functionPointer( findSymbol(handle_, "dtsRuntimeGetDrawCommand")); return true; } void RuntimePlugin::unload() { if (context_ && destroy_) { destroy_(context_); } context_ = nullptr; getApiVersion_ = nullptr; getInfo_ = nullptr; create_ = nullptr; destroy_ = nullptr; load_ = nullptr; getSkeletonInfo_ = nullptr; getAnimationName_ = nullptr; getAnimationDuration_ = nullptr; getSkinName_ = nullptr; getLastError_ = nullptr; setAnimation_ = nullptr; update_ = nullptr; getDrawCommandCount_ = nullptr; getDrawCommand_ = nullptr; if (handle_) { closeLibrary(handle_); handle_ = nullptr; } } bool RuntimePlugin::isLoaded() const { return handle_ != nullptr; } std::optional RuntimePlugin::metadata(std::string& error) const { if (!getInfo_) { error = "Runtime plugin is not loaded."; return std::nullopt; } DtsRuntimeInfo info{}; info.structSize = sizeof(info); if (getInfo_(&info) != 0 || !info.spineVersionLine || !info.adapterBuild) { error = "Runtime plugin returned invalid metadata."; return std::nullopt; } return RuntimePluginMetadata{ info.apiVersion, info.spineVersionLine, info.adapterBuild, info.capabilities }; } std::optional RuntimePlugin::loadSkeleton( const std::filesystem::path& skeletonPath, const std::filesystem::path& atlasPath, float scale, std::string& error) { if (!create_ || !destroy_ || !load_ || !getSkeletonInfo_ || !getAnimationName_ || !getAnimationDuration_ || !getSkinName_ || !getLastError_) { error = "Runtime plugin does not provide skeleton metadata capability."; return std::nullopt; } if (context_) { destroy_(context_); } context_ = create_(); if (!context_) { error = "Runtime plugin could not create a context."; return std::nullopt; } const auto skeleton = utf8Path(skeletonPath); const auto atlas = utf8Path(atlasPath); DtsLoadRequest request{sizeof(DtsLoadRequest), skeleton.c_str(), atlas.c_str(), scale}; if (load_(context_, &request) != 0) { const auto* detail = getLastError_(context_); error = detail ? detail : "Runtime plugin failed to load skeleton data."; return std::nullopt; } DtsSkeletonInfo info{}; info.structSize = sizeof(info); if (getSkeletonInfo_(context_, &info) != 0) { error = "Runtime plugin failed to return skeleton metadata."; return std::nullopt; } SkeletonMetadata result; result.boneCount = info.boneCount; result.slotCount = info.slotCount; result.eventCount = info.eventCount; result.constraintCount = info.constraintCount; result.animations.reserve(info.animationCount); for (std::uint32_t index = 0; index < info.animationCount; ++index) { const auto* name = getAnimationName_(context_, index); result.animations.push_back({name ? name : "", getAnimationDuration_(context_, index)}); } result.skins.reserve(info.skinCount); for (std::uint32_t index = 0; index < info.skinCount; ++index) { const auto* name = getSkinName_(context_, index); result.skins.emplace_back(name ? name : ""); } return result; } bool RuntimePlugin::setAnimation(const std::string& name, bool loop, std::string& error) { if (!context_ || !setAnimation_) { error = "Runtime plugin does not provide animation playback."; return false; } if (setAnimation_(context_, name.c_str(), loop ? 1 : 0) != 0) { const auto* detail = getLastError_ ? getLastError_(context_) : nullptr; error = detail ? detail : "Animation could not be selected."; return false; } return true; } std::optional> RuntimePlugin::updatePreview( float deltaSeconds, std::string& error) { if (!context_ || !update_ || !getDrawCommandCount_ || !getDrawCommand_) { error = "Runtime plugin does not provide preview drawing."; return std::nullopt; } if (update_(context_, deltaSeconds) != 0) { const auto* detail = getLastError_ ? getLastError_(context_) : nullptr; error = detail ? detail : "Preview frame could not be sampled."; return std::nullopt; } std::vector result; const auto count = getDrawCommandCount_(context_); result.reserve(count); for (std::uint32_t index = 0; index < count; ++index) { DtsDrawCommand source{}; source.structSize = sizeof(source); if (getDrawCommand_(context_, index, &source) != 0 || !source.texturePathUtf8) { error = "Runtime plugin returned an invalid draw command."; return std::nullopt; } PreviewDrawCommand command; command.texturePath = std::filesystem::path( std::u8string(reinterpret_cast(source.texturePathUtf8))); command.blendMode = source.blendMode; command.indices.assign(source.indices, source.indices + source.indexCount); command.vertices.reserve(source.vertexCount); for (std::uint32_t vertex = 0; vertex < source.vertexCount; ++vertex) { const auto& value = source.vertices[vertex]; command.vertices.push_back({value.x, value.y, value.u, value.v, value.color}); } result.push_back(std::move(command)); } return result; } } // namespace dts