diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index 320f813..f805b16 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -844,6 +844,10 @@ shared_ptr Assets::loadImage(AssetPath const& path) const { return {}; StringMap references; StringList referencePaths; + + for (auto& directives : path.directives.list()) + directives.loadOperations(); + path.directives.forEach([&](auto const& entry, Directives const& directives) { addImageOperationReferences(entry.operation, referencePaths); }); // TODO: This can definitely be better, was changed quickly to support the new Directives. diff --git a/source/core/StarAssetPath.cpp b/source/core/StarAssetPath.cpp index a99ecfa..cb607fb 100644 --- a/source/core/StarAssetPath.cpp +++ b/source/core/StarAssetPath.cpp @@ -39,11 +39,14 @@ AssetPath AssetPath::split(String const& path) { // Sub-paths must immediately follow base paths and must start with a ':', // after this point any further ':' characters are not special. if (str[end] == ':') { - size_t beg = end; - end = str.find_first_of("?", beg); - size_t len = end - beg - 1; - if (len) - components.subPath.emplace(str.substr(beg + 1, len)); + size_t beg = end + 1; + if (beg != str.size()) { + end = str.find_first_of("?", beg); + if (end == NPos && beg + 1 != str.size()) + components.subPath.emplace(str.substr(beg)); + else if (size_t len = end - beg) + components.subPath.emplace(str.substr(beg, len)); + } } if (end == NPos) diff --git a/source/core/StarDirectives.cpp b/source/core/StarDirectives.cpp index cac1eaf..a18951e 100644 --- a/source/core/StarDirectives.cpp +++ b/source/core/StarDirectives.cpp @@ -24,6 +24,14 @@ Directives::Entry::Entry(Entry const& other) { length = other.length; } +ImageOperation const& Directives::Entry::loadOperation(Shared const& parent) const { + if (operation.is()) { + try { operation = imageOperationFromString(string(parent)); } + catch (StarException const& e) { operation = ErrorImageOperation{ std::current_exception() }; } + } + return operation; +} + StringView Directives::Entry::string(Shared const& parent) const { StringView result = parent.string; result = result.utf8().substr(begin, length); @@ -54,6 +62,15 @@ Directives::Directives(const char* directives) { parse(directives); } +void Directives::loadOperations() const { + if (!shared) + return; + + MutexLocker lock(shared->mutex, true); + for (auto& entry : shared->entries) + entry.loadOperation(*shared); +} + void Directives::parse(String&& directives) { if (directives.empty()) return; @@ -63,15 +80,19 @@ void Directives::parse(String&& directives) { StringView prefix = ""; view.forEachSplitView("?", [&](StringView split, size_t beg, size_t end) { if (!split.empty()) { - try { - ImageOperation operation = imageOperationFromString(split); - newList.emplace_back(move(operation), beg, end); - } - catch (StarException const& e) { - if (beg == 0) + if (beg == 0) { + try { + ImageOperation operation = imageOperationFromString(split); + newList.emplace_back(move(operation), beg, end); + } + catch (StarException const& e) { prefix = split; - else - newList.emplace_back(ErrorImageOperation{ std::current_exception() }, beg, end); + return; + } + } + else { + ImageOperation operation = NullImageOperation(); + newList.emplace_back(move(operation), beg, end); } } }); @@ -259,10 +280,11 @@ inline Image DirectivesGroup::applyNewImage(Image const& image) const { void DirectivesGroup::applyExistingImage(Image& image) const { forEach([&](auto const& entry, Directives const& directives) { - if (auto error = entry.operation.ptr()) + ImageOperation const& operation = entry.loadOperation(*directives.shared); + if (auto error = operation.ptr()) std::rethrow_exception(error->exception); else - processImageOperation(entry.operation, image); + processImageOperation(operation, image); }); } diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp index 8af2f51..f841373 100644 --- a/source/core/StarDirectives.hpp +++ b/source/core/StarDirectives.hpp @@ -5,6 +5,7 @@ #include "StarHash.hpp" #include "StarDataStream.hpp" #include "StarStringView.hpp" +#include "StarThread.hpp" namespace Star { @@ -17,10 +18,11 @@ class Directives { public: struct Shared; struct Entry { - ImageOperation operation; + mutable ImageOperation operation; size_t begin; size_t length; + ImageOperation const& loadOperation(Shared const& parent) const; inline StringView string(Shared const& parent) const; Entry(ImageOperation&& newOperation, size_t begin, size_t end); Entry(ImageOperation const& newOperation, size_t begin, size_t end); @@ -32,6 +34,7 @@ public: String string; StringView prefix; size_t hash = 0; + mutable Mutex mutex; bool empty() const; Shared(List&& givenEntries, String&& givenString, StringView givenPrefix); @@ -42,6 +45,7 @@ public: Directives(String&& directives); Directives(const char* directives); + void loadOperations() const; void parse(String&& directives); String string() const; StringView prefix() const; diff --git a/source/core/StarImageProcessing.cpp b/source/core/StarImageProcessing.cpp index 4dfcf8a..e95a13f 100644 --- a/source/core/StarImageProcessing.cpp +++ b/source/core/StarImageProcessing.cpp @@ -198,8 +198,11 @@ ImageOperation imageOperationFromString(StringView string) { else if (hexLen == 8) { hexDecode(hexPtr, 8, c, 4); } - else - throw ImageOperationException(strf("Improper size for hex string '%s' in imageOperationFromString", StringView(hexPtr, hexLen)), false); + else if (!which || (ptr != end && ++ptr != end)) + throw ImageOperationException(strf("Improper size for hex string '%s' in imageOperationFromString", StringView(hexPtr, hexLen)), false); + else // we're in A of A=B. In vanilla only A=B pairs are evaluated, so only throw an exception if B is also there. + return move(operation); + if (which = !which) operation.colorReplaceMap[*(Vec4B*)&a] = *(Vec4B*)&b; diff --git a/source/core/StarImageProcessing.hpp b/source/core/StarImageProcessing.hpp index 0b78408..ed3319b 100644 --- a/source/core/StarImageProcessing.hpp +++ b/source/core/StarImageProcessing.hpp @@ -18,6 +18,10 @@ Image scaleBicubic(Image const& srcImage, Vec2F const& scale); StringList colorDirectivesFromConfig(JsonArray const& directives); String paletteSwapDirectivesFromConfig(Json const& swaps); +struct NullImageOperation { + +}; + struct ErrorImageOperation { std::exception_ptr exception; }; @@ -133,7 +137,7 @@ struct FlipImageOperation { Mode mode; }; -typedef Variant ImageOperation; diff --git a/source/core/StarStringView.cpp b/source/core/StarStringView.cpp index d0e0e07..1357404 100644 --- a/source/core/StarStringView.cpp +++ b/source/core/StarStringView.cpp @@ -127,7 +127,7 @@ void StringView::forEachSplitAnyView(StringView chars, SplitCallback callback) c while (true) { size_t end = m_view.find_first_of(chars.m_view, beg); if (end == NPos) { - callback(m_view.substr(beg), beg, end); + callback(m_view.substr(beg), beg, m_view.size() - beg); break; } callback(m_view.substr(beg, end - beg), beg, end - beg); @@ -143,7 +143,7 @@ void StringView::forEachSplitView(StringView pattern, SplitCallback callback) co while (true) { size_t end = m_view.find(pattern.m_view, beg); if (end == NPos) { - callback(m_view.substr(beg), beg, end); + callback(m_view.substr(beg), beg, m_view.size() - beg); break; } callback(m_view.substr(beg, end - beg), beg, end - beg); diff --git a/source/frontend/StarErrorScreen.cpp b/source/frontend/StarErrorScreen.cpp index c5d207e..9162e5b 100644 --- a/source/frontend/StarErrorScreen.cpp +++ b/source/frontend/StarErrorScreen.cpp @@ -33,7 +33,7 @@ void ErrorScreen::setMessage(String const& errorMessage) { if (!m_paneManager->isDisplayed(m_errorPane)) { m_paneManager->displayPane(PaneLayer::Window, m_errorPane, [this](PanePtr) { m_accepted = true; - }); + }); } } diff --git a/source/game/StarHumanoid.cpp b/source/game/StarHumanoid.cpp index 8e571e5..d54804a 100644 --- a/source/game/StarHumanoid.cpp +++ b/source/game/StarHumanoid.cpp @@ -517,7 +517,7 @@ List Humanoid::render() { image = strf("%s:%s.%s", m_backArmorFrameset, frameGroup, bodyStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, Vec2F()); - drawable.imagePart().addDirectives(getBackDirectives()); + drawable.imagePart().addDirectives(getBackDirectives(), true); addDrawable(move(drawable)); } @@ -550,7 +550,7 @@ List Humanoid::render() { } else image = strf("%s:%s.%s", m_backArmFrameset, frameBase(m_state), armStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); if (dance.isValid()) drawable.rotate(danceStep->backArmRotation); addDrawable(move(drawable), m_bodyFullbright); @@ -567,7 +567,7 @@ List Humanoid::render() { } else image = strf("%s:%s.%s", m_backSleeveFrameset, frameBase(m_state), armStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); - drawable.imagePart().addDirectives(getChestDirectives()); + drawable.imagePart().addDirectives(getChestDirectives(), true); if (dance.isValid()) drawable.rotate(danceStep->backArmRotation); addDrawable(move(drawable)); @@ -593,21 +593,21 @@ List Humanoid::render() { if (!m_headFrameset.empty() && !m_bodyHidden) { String image = strf("%s:normal", m_headFrameset); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); addDrawable(move(drawable), m_bodyFullbright); } if (!m_emoteFrameset.empty() && !m_bodyHidden) { String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); - drawable.imagePart().addDirectives(getEmoteDirectives()); + drawable.imagePart().addDirectives(getEmoteDirectives(), true); addDrawable(move(drawable), m_bodyFullbright); } if (!m_hairFrameset.empty() && !m_bodyHidden) { String image = strf("%s:normal", m_hairFrameset); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); - drawable.imagePart().addDirectives(getHairDirectives()).addDirectives(getHelmetMaskDirectives()); + drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true); addDrawable(move(drawable), m_bodyFullbright); } @@ -620,7 +620,7 @@ List Humanoid::render() { else image = strf("%s:%s.%s", m_bodyFrameset, frameBase(m_state), bodyStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); addDrawable(move(drawable), m_bodyFullbright); } @@ -633,7 +633,7 @@ List Humanoid::render() { else image = strf("%s:%s.%s", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}); - drawable.imagePart().addDirectives(getLegsDirectives()); + drawable.imagePart().addDirectives(getLegsDirectives(), true); addDrawable(move(drawable)); } @@ -655,28 +655,28 @@ List Humanoid::render() { if (m_state != Duck) position[1] += bobYOffset; auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); - drawable.imagePart().addDirectives(getChestDirectives()); + drawable.imagePart().addDirectives(getChestDirectives(), true); addDrawable(move(drawable)); } if (!m_facialHairFrameset.empty() && !m_bodyHidden) { String image = strf("%s:normal", m_facialHairFrameset); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); - drawable.imagePart().addDirectives(getFacialHairDirectives()).addDirectives(getHelmetMaskDirectives()); + drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true); addDrawable(move(drawable), m_bodyFullbright); } if (!m_facialMaskFrameset.empty() && !m_bodyHidden) { String image = strf("%s:normal", m_facialMaskFrameset); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); - drawable.imagePart().addDirectives(getFacialMaskDirectives()).addDirectives(getHelmetMaskDirectives()); + drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(getHelmetMaskDirectives(), true); addDrawable(move(drawable)); } if (!m_headArmorFrameset.empty()) { String image = strf("%s:normal", m_headArmorFrameset); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition); - drawable.imagePart().addDirectives(getHeadDirectives()); + drawable.imagePart().addDirectives(getHeadDirectives(), true); addDrawable(move(drawable)); } @@ -718,7 +718,7 @@ List Humanoid::render() { } else image = strf("%s:%s.%s", m_frontArmFrameset, frameBase(m_state), armStateSeq); auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); if (dance.isValid()) drawable.rotate(danceStep->frontArmRotation); addDrawable(drawable, m_bodyFullbright); @@ -736,7 +736,7 @@ List Humanoid::render() { } else image = strf("%s:%s.%s", m_frontSleeveFrameset, frameBase(m_state), armStateSeq); auto drawable = Drawable::makeImage(image, 1.0f / TilePixels, true, position); - drawable.imagePart().addDirectives(getChestDirectives()); + drawable.imagePart().addDirectives(getChestDirectives(), true); if (dance.isValid()) drawable.rotate(danceStep->frontArmRotation); addDrawable(drawable); @@ -788,20 +788,20 @@ List Humanoid::renderPortrait(PortraitMode mode) const { if (!m_backArmFrameset.empty()) { String image = strf("%s:%s", m_backArmFrameset, personality.armIdle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); addDrawable(move(drawable)); } if (dressed && !m_backSleeveFrameset.empty()) { String image = strf("%s:%s", m_backSleeveFrameset, personality.armIdle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getChestDirectives()); + drawable.imagePart().addDirectives(getChestDirectives(), true); addDrawable(move(drawable)); } if (mode != PortraitMode::Bust) { if (dressed && !m_backArmorFrameset.empty()) { String image = strf("%s:%s", m_backArmorFrameset, personality.idle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getBackDirectives()); + drawable.imagePart().addDirectives(getBackDirectives(), true); addDrawable(move(drawable)); } } @@ -810,28 +810,28 @@ List Humanoid::renderPortrait(PortraitMode mode) const { if (!m_headFrameset.empty()) { String image = strf("%s:normal", m_headFrameset); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); addDrawable(move(drawable)); } if (!m_emoteFrameset.empty()) { String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getEmoteDirectives()); + drawable.imagePart().addDirectives(getEmoteDirectives(), true); addDrawable(move(drawable)); } if (!m_hairFrameset.empty()) { String image = strf("%s:normal", m_hairFrameset); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getHairDirectives()).addDirectives(helmetMaskDirective); + drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(helmetMaskDirective, true); addDrawable(move(drawable)); } if (!m_bodyFrameset.empty()) { String image = strf("%s:%s", m_bodyFrameset, personality.idle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); addDrawable(move(drawable)); } @@ -839,14 +839,14 @@ List Humanoid::renderPortrait(PortraitMode mode) const { if (dressed && !m_legsArmorFrameset.empty()) { String image = strf("%s:%s", m_legsArmorFrameset, personality.idle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getLegsDirectives()); + drawable.imagePart().addDirectives(getLegsDirectives(), true); addDrawable(move(drawable)); } if (dressed && !m_chestArmorFrameset.empty()) { String image = strf("%s:%s", m_chestArmorFrameset, personality.idle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {}); - drawable.imagePart().addDirectives(getChestDirectives()); + drawable.imagePart().addDirectives(getChestDirectives(), true); addDrawable(move(drawable)); } } @@ -854,21 +854,21 @@ List Humanoid::renderPortrait(PortraitMode mode) const { if (!m_facialHairFrameset.empty()) { String image = strf("%s:normal", m_facialHairFrameset); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getFacialHairDirectives()).addDirectives(helmetMaskDirective); + drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(helmetMaskDirective, true); addDrawable(move(drawable)); } if (!m_facialMaskFrameset.empty()) { String image = strf("%s:normal", m_facialMaskFrameset); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getFacialMaskDirectives()).addDirectives(helmetMaskDirective); + drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(helmetMaskDirective, true); addDrawable(move(drawable)); } if (dressed && !m_headArmorFrameset.empty()) { String image = strf("%s:normal", m_headArmorFrameset); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset); - drawable.imagePart().addDirectives(getHeadDirectives()); + drawable.imagePart().addDirectives(getHeadDirectives(), true); addDrawable(move(drawable)); } @@ -876,14 +876,14 @@ List Humanoid::renderPortrait(PortraitMode mode) const { if (!m_frontArmFrameset.empty()) { String image = strf("%s:%s", m_frontArmFrameset, personality.armIdle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getBodyDirectives()); + drawable.imagePart().addDirectives(getBodyDirectives(), true); addDrawable(move(drawable)); } if (dressed && !m_frontSleeveFrameset.empty()) { String image = strf("%s:%s", m_frontSleeveFrameset, personality.armIdle); Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset); - drawable.imagePart().addDirectives(getChestDirectives()); + drawable.imagePart().addDirectives(getChestDirectives(), true); addDrawable(move(drawable)); } } diff --git a/source/game/StarImageMetadataDatabase.cpp b/source/game/StarImageMetadataDatabase.cpp index e47c841..c5ebd84 100644 --- a/source/game/StarImageMetadataDatabase.cpp +++ b/source/game/StarImageMetadataDatabase.cpp @@ -121,6 +121,8 @@ AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) { AssetPath newPath = { path.basePath, path.subPath, {} }; String filtered; + for (auto& directives : path.directives.list()) + directives.loadOperations(); path.directives.forEach([&](auto const& entry, Directives const& directives) { ImageOperation const& operation = entry.operation; if (!(operation.is() || @@ -184,6 +186,8 @@ Vec2U ImageMetadataDatabase::calculateImageSize(AssetPath const& path) const { OperationSizeAdjust(Vec2U& size) : imageSize(size), hasError(false) {}; + void operator()(NullImageOperation const&) {} + void operator()(ErrorImageOperation const&) {} void operator()(HueShiftImageOperation const&) {} @@ -231,6 +235,9 @@ Vec2U ImageMetadataDatabase::calculateImageSize(AssetPath const& path) const { OperationSizeAdjust osa(imageSize); + for (auto& directives : path.directives.list()) + directives.loadOperations(); + bool complete = path.directives.forEachAbortable([&](auto const& entry, Directives const& directives) -> bool { entry.operation.call(osa); return !osa.hasError; diff --git a/source/game/StarNetworkedAnimator.cpp b/source/game/StarNetworkedAnimator.cpp index 7ce5e16..718073f 100644 --- a/source/game/StarNetworkedAnimator.cpp +++ b/source/game/StarNetworkedAnimator.cpp @@ -656,7 +656,7 @@ List> NetworkedAnimator::drawablesWithZLevel(Vec2F const& Drawable drawable = find->second.second; auto& imagePart = drawable.imagePart(); for (Directives const& directives : baseProcessingDirectives) - imagePart.addDirectives(directives); + imagePart.addDirectives(directives, centered); drawable.transform(partTransformation(partName)); drawable.transform(globalTransformation()); drawable.fullbright = fullbright;