283 lines
9.9 KiB
C++
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
|