Add support for directly setting image assets and processing Image userdata

This commit is contained in:
Kae 2024-03-25 03:46:21 +11:00
parent ff6e349aef
commit 560ae08424
13 changed files with 145 additions and 38 deletions

View File

@ -532,7 +532,10 @@ add_subdirectory(core)
# Less general purpose code than core that is available to both the game and # Less general purpose code than core that is available to both the game and
# application modules. # 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) add_subdirectory(base)
# Platform APIs that are implemented by the application module # Platform APIs that are implemented by the application module

View File

@ -17,9 +17,11 @@ SET (star_base_HEADERS
StarMemoryAssetSource.hpp StarMemoryAssetSource.hpp
StarMixer.hpp StarMixer.hpp
StarPackedAssetSource.hpp StarPackedAssetSource.hpp
StarRootBase.hpp
StarVersion.hpp StarVersion.hpp
StarVersionOptionParser.hpp StarVersionOptionParser.hpp
StarWorldGeometry.hpp StarWorldGeometry.hpp
scripting/StarImageLuaBindings.hpp
) )
SET (star_base_SOURCES SET (star_base_SOURCES
@ -32,8 +34,10 @@ SET (star_base_SOURCES
StarMemoryAssetSource.cpp StarMemoryAssetSource.cpp
StarMixer.cpp StarMixer.cpp
StarPackedAssetSource.cpp StarPackedAssetSource.cpp
StarRootBase.cpp
StarVersionOptionParser.cpp StarVersionOptionParser.cpp
StarWorldGeometry.cpp StarWorldGeometry.cpp
scripting/StarImageLuaBindings.cpp
) )
CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp) CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)

View File

@ -145,7 +145,10 @@ Assets::Assets(Settings settings, StringList assetSources) {
ByteArray bytes; ByteArray bytes;
if (auto str = engine.luaMaybeTo<String>(data)) if (auto str = engine.luaMaybeTo<String>(data))
bytes = ByteArray(str->utf8Ptr(), str->utf8Size()); bytes = ByteArray(str->utf8Ptr(), str->utf8Size());
else { else if (auto image = engine.luaMaybeTo<Image>(data)) {
newFiles->set(path, std::move(*image));
return;
} else {
auto json = engine.luaTo<Json>(data).repr(); auto json = engine.luaTo<Json>(data).repr();
bytes = ByteArray(json.utf8Ptr(), json.utf8Size()); bytes = ByteArray(json.utf8Ptr(), json.utf8Size());
} }
@ -855,6 +858,19 @@ ByteArray Assets::read(String const& path) const {
throw AssetException(strf("No such asset '{}'", path)); 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<MemoryAssetSource>(p->source))
image = memorySource->image(p->sourceName);
if (!image)
image = make_shared<Image>(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<Json> const external) const { Json Assets::checkPatchArray(String const& path, AssetSourcePtr const& source, Json const result, JsonArray const patchData, Maybe<Json> const external) const {
auto externalRef = external.value(); auto externalRef = external.value();
auto newResult = result; auto newResult = result;
@ -1140,7 +1156,7 @@ shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
} else { } else {
auto imageData = make_shared<ImageData>(); auto imageData = make_shared<ImageData>();
imageData->image = unlockDuring([&]() { imageData->image = unlockDuring([&]() {
return make_shared<Image>(Image::readPng(open(path.basePath))); return readImage(path.basePath);
}); });
imageData->frames = bestFramesSpecification(path.basePath); imageData->frames = bestFramesSpecification(path.basePath);

View File

@ -275,6 +275,7 @@ private:
IODevicePtr open(String const& basePath) const; IODevicePtr open(String const& basePath) const;
ByteArray read(String const& basePath) const; ByteArray read(String const& basePath) const;
ImageConstPtr readImage(String const& path) const;
Json readJson(String const& basePath) const; Json readJson(String const& basePath) const;
Json checkPatchArray(String const& path, AssetSourcePtr const& source, Json const result, JsonArray const patchData, Maybe<Json> const external) const; Json checkPatchArray(String const& path, AssetSourcePtr const& source, Json const result, JsonArray const patchData, Maybe<Json> const external) const;

View File

@ -1,7 +1,7 @@
#include "StarMemoryAssetSource.hpp" #include "StarMemoryAssetSource.hpp"
#include "StarDataStreamDevices.hpp" #include "StarDataStreamDevices.hpp"
#include "StarDataStreamExtra.hpp" #include "StarDataStreamExtra.hpp"
#include "StarSha256.hpp" #include "StarImage.hpp"
namespace Star { namespace Star {
@ -21,11 +21,17 @@ StringList MemoryAssetSource::assetPaths() const {
IODevicePtr MemoryAssetSource::open(String const& path) { IODevicePtr MemoryAssetSource::open(String const& path) {
struct AssetReader : public IODevice { 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 { size_t read(char* data, size_t len) override {
len = min<StreamOffset>(len, StreamOffset(assetData->size()) - assetPos); len = min<StreamOffset>(len, StreamOffset(assetSize) - assetPos);
assetData->copyTo(data, len); memcpy(data, assetData + assetPos, len);
assetPos += len;
return len; return len;
} }
@ -33,25 +39,26 @@ IODevicePtr MemoryAssetSource::open(String const& path) {
throw IOException("Assets IODevices are read-only"); throw IOException("Assets IODevices are read-only");
} }
StreamOffset size() override { return assetData->size(); } StreamOffset size() override { return assetSize; }
StreamOffset pos() override { return assetPos; } StreamOffset pos() override { return assetPos; }
String deviceName() const override { return name; } String deviceName() const override { return name; }
bool atEnd() override { bool atEnd() override {
return assetPos >= assetData->size(); return assetPos >= assetSize;
} }
void seek(StreamOffset p, IOSeek mode) override { void seek(StreamOffset p, IOSeek mode) override {
if (mode == IOSeek::Absolute) if (mode == IOSeek::Absolute)
assetPos = p; assetPos = p;
else if (mode == IOSeek::Relative) else if (mode == IOSeek::Relative)
assetPos = clamp<StreamOffset>(assetPos + p, 0, assetData->size()); assetPos = clamp<StreamOffset>(assetPos + p, 0, assetSize);
else else
assetPos = clamp<StreamOffset>(assetPos - p, 0, assetData->size()); assetPos = clamp<StreamOffset>(assetPos - p, 0, assetSize);
} }
ByteArrayPtr assetData; char* assetData;
size_t assetSize;
StreamOffset assetPos = 0; StreamOffset assetPos = 0;
String name; String name;
}; };
@ -59,8 +66,12 @@ IODevicePtr MemoryAssetSource::open(String const& path) {
auto p = m_files.ptr(path); auto p = m_files.ptr(path);
if (!p) if (!p)
throw AssetSourceException::format("Requested file '{}' does not exist in memory", path); throw AssetSourceException::format("Requested file '{}' does not exist in memory", path);
else if (auto byteArray = p->ptr<ByteArray>())
return make_shared<AssetReader>(*p, path); return make_shared<AssetReader>(byteArray->ptr(), byteArray->size(), path);
else {
auto image = p->get<ImagePtr>().get();
return make_shared<AssetReader>((char*)image->data(), image->width() * image->height() * image->bytesPerPixel(), path);
}
} }
bool MemoryAssetSource::empty() const { bool MemoryAssetSource::empty() const {
@ -76,15 +87,37 @@ bool MemoryAssetSource::erase(String const& path) {
} }
void MemoryAssetSource::set(String const& path, ByteArray data) { void MemoryAssetSource::set(String const& path, ByteArray data) {
m_files[path] = make_shared<ByteArray>(std::move(data)); m_files[path] = std::move(data);
}
void MemoryAssetSource::set(String const& path, Image const& image) {
m_files[path] = make_shared<Image>(image);
}
void MemoryAssetSource::set(String const& path, Image&& image) {
m_files[path] = make_shared<Image>(std::move(image));
} }
ByteArray MemoryAssetSource::read(String const& path) { ByteArray MemoryAssetSource::read(String const& path) {
auto p = m_files.ptr(path); auto p = m_files.ptr(path);
if (!p) if (!p)
throw AssetSourceException::format("Requested file '{}' does not exist in memory", path); throw AssetSourceException::format("Requested file '{}' does not exist in memory", path);
else if (auto bytes = p->ptr<ByteArray>())
return *bytes;
else {
Image const* image = p->get<ImagePtr>().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<ImagePtr>())
return *imagePtr;
else else
return *p->get(); // this is a double indirection, and that freaking sucks!! return nullptr;
} }
} }

View File

@ -6,6 +6,7 @@
namespace Star { namespace Star {
STAR_CLASS(MemoryAssetSource); STAR_CLASS(MemoryAssetSource);
STAR_CLASS(Image);
class MemoryAssetSource : public AssetSource { class MemoryAssetSource : public AssetSource {
public: public:
@ -15,17 +16,23 @@ public:
JsonObject metadata() const override; JsonObject metadata() const override;
StringList assetPaths() 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; IODevicePtr open(String const& path) override;
bool empty() const; bool empty() const;
bool contains(String const& path) const; bool contains(String const& path) const;
bool erase(String const& path); bool erase(String const& path);
void set(String const& path, ByteArray data); 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; ByteArray read(String const& path) override;
ImageConstPtr image(String const& path);
private: private:
typedef Variant<ByteArray, ImagePtr> FileEntry;
String m_name; String m_name;
JsonObject m_metadata; JsonObject m_metadata;
StringMap<ByteArrayPtr> m_files; StringMap<FileEntry> m_files;
}; };
} }

View File

@ -0,0 +1,23 @@
#include "StarRootBase.hpp"
namespace Star {
atomic<RootBase*> 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");
}
}

View File

@ -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<RootBase*> s_singleton;
};
}

View File

@ -1,6 +1,7 @@
#include "StarImageLuaBindings.hpp" #include "StarImageLuaBindings.hpp"
#include "StarLuaConverters.hpp" #include "StarLuaConverters.hpp"
#include "StarImage.hpp" #include "StarImage.hpp"
#include "StarRootBase.hpp"
namespace Star { namespace Star {
@ -22,6 +23,15 @@ LuaMethods<Image> LuaUserDataMethods<Image>::make() {
return image.subImage(min, size); 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; return methods;
} }

View File

@ -127,7 +127,6 @@ SET (star_core_HEADERS
StarWorkerPool.hpp StarWorkerPool.hpp
StarXXHash.hpp StarXXHash.hpp
StarZSTDCompression.hpp StarZSTDCompression.hpp
scripting/StarImageLuaBindings.hpp
scripting/StarUtilityLuaBindings.hpp scripting/StarUtilityLuaBindings.hpp
) )
@ -185,7 +184,6 @@ SET (star_core_SOURCES
StarUuid.cpp StarUuid.cpp
StarWorkerPool.cpp StarWorkerPool.cpp
StarZSTDCompression.cpp StarZSTDCompression.cpp
scripting/StarImageLuaBindings.cpp
scripting/StarUtilityLuaBindings.cpp scripting/StarUtilityLuaBindings.cpp
) )

View File

@ -60,25 +60,19 @@ namespace {
unsigned const RootLoadThreads = 4; unsigned const RootLoadThreads = 4;
} }
atomic<Root*> Root::s_singleton;
Root* Root::singletonPtr() { Root* Root::singletonPtr() {
return s_singleton.load(); return dynamic_cast<Root*>(s_singleton.load());
} }
Root& Root::singleton() { Root& Root::singleton() {
auto ptr = s_singleton.load(); auto ptr = singletonPtr();
if (!ptr) if (!ptr)
throw RootException("Root::singleton() called with no Root instance available"); throw RootException("Root::singleton() called with no Root instance available");
else else
return *ptr; return *ptr;
} }
Root::Root(Settings settings) { Root::Root(Settings settings) : RootBase() {
Root* oldRoot = nullptr;
if (!s_singleton.compare_exchange_strong(oldRoot, this))
throw RootException("Singleton Root has been constructed twice");
m_settings = std::move(settings); m_settings = std::move(settings);
if (m_settings.runtimeConfigFile) if (m_settings.runtimeConfigFile)
m_runtimeConfigFile = toStoragePath(*m_settings.runtimeConfigFile); m_runtimeConfigFile = toStoragePath(*m_settings.runtimeConfigFile);

View File

@ -1,14 +1,13 @@
#pragma once #pragma once
#include "StarRootBase.hpp"
#include "StarJson.hpp" #include "StarJson.hpp"
#include "StarLogging.hpp" #include "StarLogging.hpp"
#include "StarListener.hpp" #include "StarListener.hpp"
#include "StarAssets.hpp"
#include "StarConfiguration.hpp" #include "StarConfiguration.hpp"
namespace Star { namespace Star {
STAR_CLASS(Configuration);
STAR_CLASS(ItemDatabase); STAR_CLASS(ItemDatabase);
STAR_CLASS(MaterialDatabase); STAR_CLASS(MaterialDatabase);
STAR_CLASS(ObjectDatabase); STAR_CLASS(ObjectDatabase);
@ -50,15 +49,13 @@ STAR_CLASS(CollectionDatabase);
STAR_CLASS(Root); STAR_CLASS(Root);
STAR_EXCEPTION(RootException, StarException);
// Singleton Root object for starbound providing access to the unique // Singleton Root object for starbound providing access to the unique
// Configuration class, as well as the assets, root factories, and databases. // Configuration class, as well as the assets, root factories, and databases.
// Root, and all members of Root, should be thread safe. Root initialization // 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 // 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 // thread, and all Root dependent code in any thread should be finished before
// letting Root destruct. // letting Root destruct.
class Root { class Root : public RootBase {
public: public:
struct Settings { struct Settings {
Assets::Settings assetsSettings; Assets::Settings assetsSettings;
@ -135,8 +132,8 @@ public:
// All of the Root member accessors are safe to call at any time after Root // 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. // initialization, if they are not loaded they will load before returning.
AssetsConstPtr assets(); AssetsConstPtr assets() override;
ConfigurationPtr configuration(); ConfigurationPtr configuration() override;
ObjectDatabaseConstPtr objectDatabase(); ObjectDatabaseConstPtr objectDatabase();
PlantDatabaseConstPtr plantDatabase(); PlantDatabaseConstPtr plantDatabase();
@ -164,7 +161,6 @@ public:
TreasureDatabaseConstPtr treasureDatabase(); TreasureDatabaseConstPtr treasureDatabase();
DungeonDefinitionsConstPtr dungeonDefinitions(); DungeonDefinitionsConstPtr dungeonDefinitions();
TilesetDatabaseConstPtr tilesetDatabase(); TilesetDatabaseConstPtr tilesetDatabase();
StatisticsDatabaseConstPtr statisicsDatabase();
StatisticsDatabaseConstPtr statisticsDatabase(); StatisticsDatabaseConstPtr statisticsDatabase();
EmoteProcessorConstPtr emoteProcessor(); EmoteProcessorConstPtr emoteProcessor();
SpeciesDatabaseConstPtr speciesDatabase(); SpeciesDatabaseConstPtr speciesDatabase();
@ -191,8 +187,6 @@ private:
// m_configurationMutex must be held when calling // m_configurationMutex must be held when calling
void writeConfig(); void writeConfig();
static atomic<Root*> s_singleton;
Settings m_settings; Settings m_settings;
Mutex m_modsMutex; Mutex m_modsMutex;