From 560ae08424956bb495bc2453973467d138029c7c Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Mon, 25 Mar 2024 03:46:21 +1100 Subject: [PATCH] Add support for directly setting image assets and processing Image userdata --- source/CMakeLists.txt | 5 +- source/base/CMakeLists.txt | 4 ++ source/base/StarAssets.cpp | 20 ++++++- source/base/StarAssets.hpp | 1 + source/base/StarMemoryAssetSource.cpp | 59 +++++++++++++++---- source/base/StarMemoryAssetSource.hpp | 9 ++- source/base/StarRootBase.cpp | 23 ++++++++ source/base/StarRootBase.hpp | 24 ++++++++ .../scripting/StarImageLuaBindings.cpp | 10 ++++ .../scripting/StarImageLuaBindings.hpp | 0 source/core/CMakeLists.txt | 2 - source/game/StarRoot.cpp | 12 +--- source/game/StarRoot.hpp | 14 ++--- 13 files changed, 145 insertions(+), 38 deletions(-) create mode 100644 source/base/StarRootBase.cpp create mode 100644 source/base/StarRootBase.hpp rename source/{core => base}/scripting/StarImageLuaBindings.cpp (71%) rename source/{core => base}/scripting/StarImageLuaBindings.hpp (100%) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d0fb879..68b65e8 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -532,7 +532,10 @@ add_subdirectory(core) # Less general purpose code than core that is available to both the game and # application modules. -set(STAR_BASE_INCLUDES ${PROJECT_SOURCE_DIR}/base) +set(STAR_BASE_INCLUDES + ${PROJECT_SOURCE_DIR}/base + ${PROJECT_SOURCE_DIR}/base/scripting +) add_subdirectory(base) # Platform APIs that are implemented by the application module diff --git a/source/base/CMakeLists.txt b/source/base/CMakeLists.txt index 8f4d18b..e813e45 100644 --- a/source/base/CMakeLists.txt +++ b/source/base/CMakeLists.txt @@ -17,9 +17,11 @@ SET (star_base_HEADERS StarMemoryAssetSource.hpp StarMixer.hpp StarPackedAssetSource.hpp + StarRootBase.hpp StarVersion.hpp StarVersionOptionParser.hpp StarWorldGeometry.hpp + scripting/StarImageLuaBindings.hpp ) SET (star_base_SOURCES @@ -32,8 +34,10 @@ SET (star_base_SOURCES StarMemoryAssetSource.cpp StarMixer.cpp StarPackedAssetSource.cpp + StarRootBase.cpp StarVersionOptionParser.cpp StarWorldGeometry.cpp + scripting/StarImageLuaBindings.cpp ) CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index d4b7228..9718ec8 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -145,7 +145,10 @@ Assets::Assets(Settings settings, StringList assetSources) { ByteArray bytes; if (auto str = engine.luaMaybeTo(data)) bytes = ByteArray(str->utf8Ptr(), str->utf8Size()); - else { + else if (auto image = engine.luaMaybeTo(data)) { + newFiles->set(path, std::move(*image)); + return; + } else { auto json = engine.luaTo(data).repr(); bytes = ByteArray(json.utf8Ptr(), json.utf8Size()); } @@ -855,6 +858,19 @@ ByteArray Assets::read(String const& path) const { throw AssetException(strf("No such asset '{}'", path)); } +ImageConstPtr Assets::readImage(String const& path) const { + if (auto p = m_files.ptr(path)) { + ImageConstPtr image; + if (auto memorySource = as(p->source)) + image = memorySource->image(p->sourceName); + if (!image) + image = make_shared(Image::readPng(p->source->open(p->sourceName))); + return image; + } + throw AssetException(strf("No such asset '{}'", path)); +} + + Json Assets::checkPatchArray(String const& path, AssetSourcePtr const& source, Json const result, JsonArray const patchData, Maybe const external) const { auto externalRef = external.value(); auto newResult = result; @@ -1140,7 +1156,7 @@ shared_ptr Assets::loadImage(AssetPath const& path) const { } else { auto imageData = make_shared(); imageData->image = unlockDuring([&]() { - return make_shared(Image::readPng(open(path.basePath))); + return readImage(path.basePath); }); imageData->frames = bestFramesSpecification(path.basePath); diff --git a/source/base/StarAssets.hpp b/source/base/StarAssets.hpp index 8cfbc52..952e024 100644 --- a/source/base/StarAssets.hpp +++ b/source/base/StarAssets.hpp @@ -275,6 +275,7 @@ private: IODevicePtr open(String const& basePath) const; ByteArray read(String const& basePath) const; + ImageConstPtr readImage(String const& path) const; Json readJson(String const& basePath) const; Json checkPatchArray(String const& path, AssetSourcePtr const& source, Json const result, JsonArray const patchData, Maybe const external) const; diff --git a/source/base/StarMemoryAssetSource.cpp b/source/base/StarMemoryAssetSource.cpp index 17e3749..a0c220d 100644 --- a/source/base/StarMemoryAssetSource.cpp +++ b/source/base/StarMemoryAssetSource.cpp @@ -1,7 +1,7 @@ #include "StarMemoryAssetSource.hpp" #include "StarDataStreamDevices.hpp" #include "StarDataStreamExtra.hpp" -#include "StarSha256.hpp" +#include "StarImage.hpp" namespace Star { @@ -21,11 +21,17 @@ StringList MemoryAssetSource::assetPaths() const { IODevicePtr MemoryAssetSource::open(String const& path) { struct AssetReader : public IODevice { - AssetReader(ByteArrayPtr assetData, String name) : assetData(assetData), name(name) { setMode(IOMode::Read); } + AssetReader(char* assetData, size_t assetSize, String name) { + this->assetData = assetData; + this->assetSize = assetSize; + this->name = std::move(name); + setMode(IOMode::Read); + } size_t read(char* data, size_t len) override { - len = min(len, StreamOffset(assetData->size()) - assetPos); - assetData->copyTo(data, len); + len = min(len, StreamOffset(assetSize) - assetPos); + memcpy(data, assetData + assetPos, len); + assetPos += len; return len; } @@ -33,25 +39,26 @@ IODevicePtr MemoryAssetSource::open(String const& path) { throw IOException("Assets IODevices are read-only"); } - StreamOffset size() override { return assetData->size(); } + StreamOffset size() override { return assetSize; } StreamOffset pos() override { return assetPos; } String deviceName() const override { return name; } bool atEnd() override { - return assetPos >= assetData->size(); + return assetPos >= assetSize; } void seek(StreamOffset p, IOSeek mode) override { if (mode == IOSeek::Absolute) assetPos = p; else if (mode == IOSeek::Relative) - assetPos = clamp(assetPos + p, 0, assetData->size()); + assetPos = clamp(assetPos + p, 0, assetSize); else - assetPos = clamp(assetPos - p, 0, assetData->size()); + assetPos = clamp(assetPos - p, 0, assetSize); } - ByteArrayPtr assetData; + char* assetData; + size_t assetSize; StreamOffset assetPos = 0; String name; }; @@ -59,8 +66,12 @@ IODevicePtr MemoryAssetSource::open(String const& path) { auto p = m_files.ptr(path); if (!p) throw AssetSourceException::format("Requested file '{}' does not exist in memory", path); - - return make_shared(*p, path); + else if (auto byteArray = p->ptr()) + return make_shared(byteArray->ptr(), byteArray->size(), path); + else { + auto image = p->get().get(); + return make_shared((char*)image->data(), image->width() * image->height() * image->bytesPerPixel(), path); + } } bool MemoryAssetSource::empty() const { @@ -76,15 +87,37 @@ bool MemoryAssetSource::erase(String const& path) { } void MemoryAssetSource::set(String const& path, ByteArray data) { - m_files[path] = make_shared(std::move(data)); + m_files[path] = std::move(data); +} + +void MemoryAssetSource::set(String const& path, Image const& image) { + m_files[path] = make_shared(image); +} + +void MemoryAssetSource::set(String const& path, Image&& image) { + m_files[path] = make_shared(std::move(image)); } ByteArray MemoryAssetSource::read(String const& path) { auto p = m_files.ptr(path); if (!p) throw AssetSourceException::format("Requested file '{}' does not exist in memory", path); + else if (auto bytes = p->ptr()) + return *bytes; + else { + Image const* image = p->get().get(); + return ByteArray((char const*)image->data(), image->width() * image->height() * image->bytesPerPixel()); + } +} + +ImageConstPtr MemoryAssetSource::image(String const& path) { + auto p = m_files.ptr(path); + if (!p) + throw AssetSourceException::format("Requested file '{}' does not exist in memory", path); + else if (auto imagePtr = p->ptr()) + return *imagePtr; else - return *p->get(); // this is a double indirection, and that freaking sucks!! + return nullptr; } } diff --git a/source/base/StarMemoryAssetSource.hpp b/source/base/StarMemoryAssetSource.hpp index e5ef740..1fc5733 100644 --- a/source/base/StarMemoryAssetSource.hpp +++ b/source/base/StarMemoryAssetSource.hpp @@ -6,6 +6,7 @@ namespace Star { STAR_CLASS(MemoryAssetSource); +STAR_CLASS(Image); class MemoryAssetSource : public AssetSource { public: @@ -15,17 +16,23 @@ public: JsonObject metadata() const override; StringList assetPaths() const override; + // do not use the returned IODevice after the file is gone or bad things will happen IODevicePtr open(String const& path) override; bool empty() const; bool contains(String const& path) const; bool erase(String const& path); void set(String const& path, ByteArray data); + void set(String const& path, Image const& image); + void set(String const& path, Image&& image); ByteArray read(String const& path) override; + ImageConstPtr image(String const& path); private: + typedef Variant FileEntry; + String m_name; JsonObject m_metadata; - StringMap m_files; + StringMap m_files; }; } diff --git a/source/base/StarRootBase.cpp b/source/base/StarRootBase.cpp new file mode 100644 index 0000000..e6bd99d --- /dev/null +++ b/source/base/StarRootBase.cpp @@ -0,0 +1,23 @@ +#include "StarRootBase.hpp" + +namespace Star { + atomic RootBase::s_singleton; + + RootBase* RootBase::singletonPtr() { + return s_singleton.load(); + } + + RootBase& RootBase::singleton() { + auto ptr = s_singleton.load(); + if (!ptr) + throw RootException("RootBase::singleton() called with no Root instance available"); + else + return *ptr; + } + + RootBase::RootBase() { + RootBase* oldRoot = nullptr; + if (!s_singleton.compare_exchange_strong(oldRoot, this)) + throw RootException("Singleton Root has been constructed twice"); + } +} \ No newline at end of file diff --git a/source/base/StarRootBase.hpp b/source/base/StarRootBase.hpp new file mode 100644 index 0000000..48a6a5e --- /dev/null +++ b/source/base/StarRootBase.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "StarAssets.hpp" + +namespace Star { + +STAR_CLASS(Configuration); + +STAR_EXCEPTION(RootException, StarException); + +class RootBase { +public: + static RootBase* singletonPtr(); + static RootBase& singleton(); + + virtual AssetsConstPtr assets() = 0; + virtual ConfigurationPtr configuration() = 0; +protected: + RootBase(); + + static atomic s_singleton; +}; + +} \ No newline at end of file diff --git a/source/core/scripting/StarImageLuaBindings.cpp b/source/base/scripting/StarImageLuaBindings.cpp similarity index 71% rename from source/core/scripting/StarImageLuaBindings.cpp rename to source/base/scripting/StarImageLuaBindings.cpp index 83fb8d1..0f5d1e4 100644 --- a/source/core/scripting/StarImageLuaBindings.cpp +++ b/source/base/scripting/StarImageLuaBindings.cpp @@ -1,6 +1,7 @@ #include "StarImageLuaBindings.hpp" #include "StarLuaConverters.hpp" #include "StarImage.hpp" +#include "StarRootBase.hpp" namespace Star { @@ -22,6 +23,15 @@ LuaMethods LuaUserDataMethods::make() { return image.subImage(min, size); }); + methods.registerMethod("process", [](Image& image, String const& directives) { + return processImageOperations(parseImageOperations(directives), image, [](String const& path) -> Image const* { + if (auto root = RootBase::singletonPtr()) + return root->assets()->image(path).get(); + else + return nullptr; + }); + }); + return methods; } diff --git a/source/core/scripting/StarImageLuaBindings.hpp b/source/base/scripting/StarImageLuaBindings.hpp similarity index 100% rename from source/core/scripting/StarImageLuaBindings.hpp rename to source/base/scripting/StarImageLuaBindings.hpp diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index eda2acc..fb31fca 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -127,7 +127,6 @@ SET (star_core_HEADERS StarWorkerPool.hpp StarXXHash.hpp StarZSTDCompression.hpp - scripting/StarImageLuaBindings.hpp scripting/StarUtilityLuaBindings.hpp ) @@ -185,7 +184,6 @@ SET (star_core_SOURCES StarUuid.cpp StarWorkerPool.cpp StarZSTDCompression.cpp - scripting/StarImageLuaBindings.cpp scripting/StarUtilityLuaBindings.cpp ) diff --git a/source/game/StarRoot.cpp b/source/game/StarRoot.cpp index e22f184..4d67563 100644 --- a/source/game/StarRoot.cpp +++ b/source/game/StarRoot.cpp @@ -60,25 +60,19 @@ namespace { unsigned const RootLoadThreads = 4; } -atomic Root::s_singleton; - Root* Root::singletonPtr() { - return s_singleton.load(); + return dynamic_cast(s_singleton.load()); } Root& Root::singleton() { - auto ptr = s_singleton.load(); + auto ptr = singletonPtr(); if (!ptr) throw RootException("Root::singleton() called with no Root instance available"); else return *ptr; } -Root::Root(Settings settings) { - Root* oldRoot = nullptr; - if (!s_singleton.compare_exchange_strong(oldRoot, this)) - throw RootException("Singleton Root has been constructed twice"); - +Root::Root(Settings settings) : RootBase() { m_settings = std::move(settings); if (m_settings.runtimeConfigFile) m_runtimeConfigFile = toStoragePath(*m_settings.runtimeConfigFile); diff --git a/source/game/StarRoot.hpp b/source/game/StarRoot.hpp index 576fe41..8015362 100644 --- a/source/game/StarRoot.hpp +++ b/source/game/StarRoot.hpp @@ -1,14 +1,13 @@ #pragma once +#include "StarRootBase.hpp" #include "StarJson.hpp" #include "StarLogging.hpp" #include "StarListener.hpp" -#include "StarAssets.hpp" #include "StarConfiguration.hpp" namespace Star { -STAR_CLASS(Configuration); STAR_CLASS(ItemDatabase); STAR_CLASS(MaterialDatabase); STAR_CLASS(ObjectDatabase); @@ -50,15 +49,13 @@ STAR_CLASS(CollectionDatabase); STAR_CLASS(Root); -STAR_EXCEPTION(RootException, StarException); - // Singleton Root object for starbound providing access to the unique // Configuration class, as well as the assets, root factories, and databases. // Root, and all members of Root, should be thread safe. Root initialization // should be completed before any code dependent on Root is started in any // thread, and all Root dependent code in any thread should be finished before // letting Root destruct. -class Root { +class Root : public RootBase { public: struct Settings { Assets::Settings assetsSettings; @@ -135,8 +132,8 @@ public: // All of the Root member accessors are safe to call at any time after Root // initialization, if they are not loaded they will load before returning. - AssetsConstPtr assets(); - ConfigurationPtr configuration(); + AssetsConstPtr assets() override; + ConfigurationPtr configuration() override; ObjectDatabaseConstPtr objectDatabase(); PlantDatabaseConstPtr plantDatabase(); @@ -164,7 +161,6 @@ public: TreasureDatabaseConstPtr treasureDatabase(); DungeonDefinitionsConstPtr dungeonDefinitions(); TilesetDatabaseConstPtr tilesetDatabase(); - StatisticsDatabaseConstPtr statisicsDatabase(); StatisticsDatabaseConstPtr statisticsDatabase(); EmoteProcessorConstPtr emoteProcessor(); SpeciesDatabaseConstPtr speciesDatabase(); @@ -191,8 +187,6 @@ private: // m_configurationMutex must be held when calling void writeConfig(); - static atomic s_singleton; - Settings m_settings; Mutex m_modsMutex;