Files
SpineParticlesWeb/DateToSpine/src/preview/RuntimePlugin.cpp
T
2026-09-07 21:29:15 +08:00

283 lines
9.9 KiB
C++

#include "preview/RuntimePlugin.hpp"
#include <cstring>
#include <utility>
#if defined(_WIN32)
#define NOMINMAX
#include <windows.h>
#else
#include <dlfcn.h>
#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<HMODULE>(handle));
#else
dlclose(handle);
#endif
}
void* findSymbol(void* handle, const char* name) {
#if defined(_WIN32)
return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle), name));
#else
return dlsym(handle, name);
#endif
}
template <typename Function>
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<const char*>(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<GetApiVersion>(findSymbol(handle_, "dtsRuntimeGetApiVersion"));
getInfo_ = functionPointer<GetInfo>(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<Create>(findSymbol(handle_, "dtsRuntimeCreate"));
destroy_ = functionPointer<Destroy>(findSymbol(handle_, "dtsRuntimeDestroy"));
load_ = functionPointer<Load>(findSymbol(handle_, "dtsRuntimeLoad"));
getSkeletonInfo_ = functionPointer<GetSkeletonInfo>(
findSymbol(handle_, "dtsRuntimeGetSkeletonInfo"));
getAnimationName_ = functionPointer<GetName>(
findSymbol(handle_, "dtsRuntimeGetAnimationName"));
getAnimationDuration_ = functionPointer<GetDuration>(
findSymbol(handle_, "dtsRuntimeGetAnimationDuration"));
getSkinName_ = functionPointer<GetName>(findSymbol(handle_, "dtsRuntimeGetSkinName"));
getLastError_ = functionPointer<GetLastError>(findSymbol(handle_, "dtsRuntimeGetLastError"));
setAnimation_ = functionPointer<SetAnimation>(findSymbol(handle_, "dtsRuntimeSetAnimation"));
update_ = functionPointer<Update>(findSymbol(handle_, "dtsRuntimeUpdate"));
getDrawCommandCount_ = functionPointer<GetDrawCommandCount>(
findSymbol(handle_, "dtsRuntimeGetDrawCommandCount"));
getDrawCommand_ = functionPointer<GetDrawCommand>(
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<RuntimePluginMetadata> 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<SkeletonMetadata> 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<std::vector<PreviewDrawCommand>> 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<PreviewDrawCommand> 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<const char8_t*>(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