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
# 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

View File

@ -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)

View File

@ -145,7 +145,10 @@ Assets::Assets(Settings settings, StringList assetSources) {
ByteArray bytes;
if (auto str = engine.luaMaybeTo<String>(data))
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();
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<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 {
auto externalRef = external.value();
auto newResult = result;
@ -1140,7 +1156,7 @@ shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
} else {
auto imageData = make_shared<ImageData>();
imageData->image = unlockDuring([&]() {
return make_shared<Image>(Image::readPng(open(path.basePath)));
return readImage(path.basePath);
});
imageData->frames = bestFramesSpecification(path.basePath);

View File

@ -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<Json> const external) const;

View File

@ -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<StreamOffset>(len, StreamOffset(assetData->size()) - assetPos);
assetData->copyTo(data, len);
len = min<StreamOffset>(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<StreamOffset>(assetPos + p, 0, assetData->size());
assetPos = clamp<StreamOffset>(assetPos + p, 0, assetSize);
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;
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<AssetReader>(*p, path);
else if (auto byteArray = p->ptr<ByteArray>())
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 {
@ -76,15 +87,37 @@ bool MemoryAssetSource::erase(String const& path) {
}
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) {
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<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
return *p->get(); // this is a double indirection, and that freaking sucks!!
return nullptr;
}
}

View File

@ -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<ByteArray, ImagePtr> FileEntry;
String m_name;
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 "StarLuaConverters.hpp"
#include "StarImage.hpp"
#include "StarRootBase.hpp"
namespace Star {
@ -22,6 +23,15 @@ LuaMethods<Image> LuaUserDataMethods<Image>::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;
}

View File

@ -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
)

View File

@ -60,25 +60,19 @@ namespace {
unsigned const RootLoadThreads = 4;
}
atomic<Root*> Root::s_singleton;
Root* Root::singletonPtr() {
return s_singleton.load();
return dynamic_cast<Root*>(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);

View File

@ -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<Root*> s_singleton;
Settings m_settings;
Mutex m_modsMutex;