From 8556bda68cb9b3a9efdebf5a25d25a7f239857b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 19 Jul 2026 21:27:30 -0300 Subject: [PATCH 01/18] migrate texture consumers to shared ownership - return TexturePtr from texture creation and lookup APIs - retain texture handles in regions, atlases, fonts, framebuffers, sprites, UI, and maps - keep queued batch textures alive until rendering completes - preserve asynchronous atlas load results directly - replace TextureLoader unload semantics with reset - remove public texture deletion APIs and obsolete Sprite ownership flags - migrate bundled tools, examples, tests, ecode, and diagnostics - document Stage 2 completion and the temporary factory retention boundary --- .../resource_shared_ownership_architecture.md | 25 +++-- include/eepp/graphics/batchrenderer.hpp | 7 ++ include/eepp/graphics/font.hpp | 2 +- include/eepp/graphics/fontbmfont.hpp | 8 +- include/eepp/graphics/fontsprite.hpp | 6 +- include/eepp/graphics/fonttruetype.hpp | 12 +- include/eepp/graphics/framebuffer.hpp | 4 +- include/eepp/graphics/glyphdrawable.hpp | 8 +- include/eepp/graphics/ninepatch.hpp | 6 +- include/eepp/graphics/particlesystem.hpp | 3 +- include/eepp/graphics/sprite.hpp | 11 +- include/eepp/graphics/texture.hpp | 2 +- include/eepp/graphics/textureatlas.hpp | 14 +-- include/eepp/graphics/textureatlasloader.hpp | 5 +- include/eepp/graphics/texturefactory.hpp | 62 ++++------- include/eepp/graphics/textureloader.hpp | 12 +- include/eepp/graphics/textureregion.hpp | 24 ++-- include/eepp/ui/tools/uicolorpicker.hpp | 4 +- include/eepp/ui/uiicon.hpp | 2 +- include/eepp/ui/uiimage.hpp | 3 + include/eepp/ui/uinodedrawable.hpp | 4 + include/eepp/window/cursor.hpp | 1 + src/eepp/graphics/batchrenderer.cpp | 18 ++- src/eepp/graphics/drawablesearcher.cpp | 25 +++-- src/eepp/graphics/fontbmfont.cpp | 13 +-- src/eepp/graphics/fontsprite.cpp | 7 +- src/eepp/graphics/fonttruetype.cpp | 9 +- src/eepp/graphics/framebuffer.cpp | 7 +- src/eepp/graphics/framebufferfbo.cpp | 8 +- src/eepp/graphics/glyphdrawable.cpp | 10 +- src/eepp/graphics/ninepatch.cpp | 15 ++- src/eepp/graphics/particlesystem.cpp | 4 +- src/eepp/graphics/sprite.cpp | 86 +++++--------- src/eepp/graphics/text.cpp | 4 +- src/eepp/graphics/texture.cpp | 4 +- src/eepp/graphics/textureatlas.cpp | 22 ++-- src/eepp/graphics/textureatlasloader.cpp | 22 ++-- src/eepp/graphics/texturefactory.cpp | 105 +++++------------- src/eepp/graphics/textureloader.cpp | 23 ++-- src/eepp/graphics/textureregion.cpp | 40 +++---- src/eepp/ui/tools/uicolorpicker.cpp | 10 +- src/eepp/ui/tools/uidiffview.cpp | 1 - src/eepp/ui/tools/uiimageviewer.cpp | 2 - src/eepp/ui/uiicon.cpp | 6 +- src/eepp/ui/uiimage.cpp | 33 ++++-- src/eepp/ui/uinodedrawable.cpp | 19 +++- src/eepp/ui/uisvg.cpp | 5 +- src/examples/sprites/sprites.cpp | 4 +- .../maps/include/eepp/maps/tilemap.hpp | 4 +- .../maps/src/eepp/maps/mapobjectlayer.cpp | 2 +- src/modules/maps/src/eepp/maps/tilemap.cpp | 8 +- .../maps/src/eepp/maps/tilemaplayer.cpp | 2 +- src/tests/test_all/test.cpp | 31 ++++-- src/tests/test_all/test.hpp | 5 +- src/tests/unit_tests/fontrendering_tests.cpp | 2 +- .../resource_prerequisite_tests.cpp | 71 +++++++++--- src/tests/unit_tests/uihtml_tests.cpp | 17 +-- src/tools/ecode/settingsactions.cpp | 4 +- src/tools/uieditor/uieditor.cpp | 6 +- 59 files changed, 443 insertions(+), 436 deletions(-) diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index bdb69aca7..52190240d 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -1,7 +1,7 @@ # eepp shared-resource ownership architecture -Status: active implementation baseline; Stage 0, prerequisite fixes, and Stage 1 complete; -Stage 2 is next, 2026-07-15. +Status: active implementation baseline; Stage 0, prerequisite fixes, Stage 1, and Stage 2 complete; +Stage 3 is next, 2026-07-19. This document freezes the contracts that must be true before the public texture API is changed. The implementation may refine names and small mechanics, but changing an invariant below requires an @@ -576,6 +576,17 @@ Exit tests: ### Stage 2: one complete TexturePtr ownership cut +Status: complete, 2026-07-19. TextureFactory creation and acquisition APIs now return TexturePtr, +and TextureLoader exposes handle-based state with `reset()` replacing destructive `unload()` +semantics. TextureRegion, atlases/loaders, framebuffers, font pages and glyphs, nine-patches, +sprites, particle systems, SVG caches, UI image/background paths, maps, tools, tests, ecode, and +eeiv now retain texture handles. Atlas worker loads store their returned handles directly instead +of depending on later global lookup. BatchRenderer retains handle-aware submissions until flush; +its raw overload is limited to Texture's immediate draw path, whose queued object lifetime is +protected by display-time deferred destruction. Sprite's obsolete texture-owner flag and public +factory texture-removal APIs are removed. Factory-wide strong retention remains only as the +planned temporary bridge to Stage 3. + Change creation/acquisition APIs to return TexturePtr and migrate every required holder in the same repository-wide cut. During conversion, TextureFactory temporarily retains strong handles so an unclassified ignored result cannot silently expire. @@ -740,8 +751,8 @@ Remove raw-owning `ResourceManager` only when no subclass or consumer depends ## 12. Next implementation deliverable -Stage 1 is complete, including removal of the obsolete context-recovery APIs and TextureLoader -callback registry, weak UITextureViewer observation, and display/shutdown texture collection. The -next coding deliverable is the complete Stage 2 TexturePtr ownership cut described above: change -the public APIs and migrate every texture holder while temporary factory retention keeps the -repository behavior stable. +Stage 2 is complete with the repository-wide TexturePtr API and holder migration while temporary +factory retention preserves existing global lookup behavior. The next coding deliverable is Stage +3: introduce catalogs and Graphics scopes, migrate semantic name lookup out of TextureFactory, move +intentional persistence into catalogs/caches, and then remove the factory's temporary strong +retention. diff --git a/include/eepp/graphics/batchrenderer.hpp b/include/eepp/graphics/batchrenderer.hpp index b799f7b63..469b62a55 100644 --- a/include/eepp/graphics/batchrenderer.hpp +++ b/include/eepp/graphics/batchrenderer.hpp @@ -43,6 +43,10 @@ class EE_API BatchRenderer { void setTexture( const Texture* texture, Texture::CoordinateType coordinateType = Texture::CoordinateType::Normalized ); + /** Retains the texture until the currently queued batch has been rendered. */ + void setTexture( const TexturePtr& texture, + Texture::CoordinateType coordinateType = Texture::CoordinateType::Normalized ); + /** Set the predefined blending function to use on the batch */ void setBlendMode( const BlendMode& blend ); @@ -315,7 +319,10 @@ class EE_API BatchRenderer { VertexData* mTVertex{ nullptr }; unsigned int mNumVertex{ 0 }; + // Borrowed draw view. Handle-aware submissions retain mTextureOwner; Texture::draw() borrows + // under the contract that released textures are collected only after this batch is flushed. const Texture* mTexture{ nullptr }; + TexturePtr mTextureOwner; BlendMode mBlend{ BlendMode::Alpha() }; Vector2f mTexCoord[4]{ Vector2f::Zero, Vector2f::Zero, Vector2f::Zero, Vector2f::Zero }; diff --git a/include/eepp/graphics/font.hpp b/include/eepp/graphics/font.hpp index 92bd6b478..934c710c3 100644 --- a/include/eepp/graphics/font.hpp +++ b/include/eepp/graphics/font.hpp @@ -156,7 +156,7 @@ class EE_API Font { virtual Float getUnderlineThickness( unsigned int characterSize ) const = 0; - virtual Texture* getTexture( unsigned int characterSize ) const = 0; + virtual const TexturePtr& getTexture( unsigned int characterSize ) const = 0; virtual Uint32 getFontStyle() const; diff --git a/include/eepp/graphics/fontbmfont.hpp b/include/eepp/graphics/fontbmfont.hpp index 36489185c..1ebe751bb 100644 --- a/include/eepp/graphics/fontbmfont.hpp +++ b/include/eepp/graphics/fontbmfont.hpp @@ -37,7 +37,7 @@ class EE_API FontBMFont : public Font { const Font::Info& getInfo() const; Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic, - Float outlineThickness = 0 ) const; + Float outlineThickness = 0 ) const; GlyphDrawable* getGlyphDrawable( Uint32 codePoint, unsigned int characterSize, bool bold = false, bool italic = false, @@ -54,7 +54,7 @@ class EE_API FontBMFont : public Font { Float getUnderlineThickness( unsigned int characterSize ) const; - Texture* getTexture( unsigned int characterSize ) const; + const TexturePtr& getTexture( unsigned int characterSize ) const; bool loaded() const; @@ -71,8 +71,8 @@ class EE_API FontBMFont : public Font { GlyphTable glyphs; ///< Table mapping code points to their corresponding glyph GlyphDrawableTable - drawables; ///> Table mapping code points to their corresponding glyph drawables. - Texture* texture; ///< Texture containing the pixels of the glyphs + drawables; ///> Table mapping code points to their corresponding glyph drawables. + TexturePtr texture; ///< Texture containing the pixels of the glyphs }; void cleanup(); diff --git a/include/eepp/graphics/fontsprite.hpp b/include/eepp/graphics/fontsprite.hpp index 39cb8b06d..70cc1e53c 100644 --- a/include/eepp/graphics/fontsprite.hpp +++ b/include/eepp/graphics/fontsprite.hpp @@ -58,7 +58,7 @@ class EE_API FontSprite : public Font { Float getUnderlineThickness( unsigned int characterSize ) const; - Texture* getTexture( unsigned int characterSize ) const; + const TexturePtr& getTexture( unsigned int characterSize ) const; bool loaded() const; @@ -75,8 +75,8 @@ class EE_API FontSprite : public Font { GlyphTable glyphs; ///< Table mapping code points to their corresponding glyph GlyphDrawableTable - drawables; ///> Table mapping code points to their corresponding glyph drawables. - Texture* texture; ///< Texture containing the pixels of the glyphs + drawables; ///> Table mapping code points to their corresponding glyph drawables. + TexturePtr texture; ///< Texture containing the pixels of the glyphs }; void cleanup(); diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index fc2f21f8e..518fc3442 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -77,7 +77,7 @@ class EE_API FontTrueType : public Font { Float getUnderlineThickness( unsigned int characterSize ) const; - Texture* getTexture( unsigned int characterSize ) const; + const TexturePtr& getTexture( unsigned int characterSize ) const; bool loaded() const; @@ -201,11 +201,11 @@ class EE_API FontTrueType : public Font { GlyphTable glyphs; ///< Table mapping code points to their corresponding glyph GlyphDrawableTable - drawables; ///> Table mapping code points to their corresponding glyph drawables. - Texture* texture{ nullptr }; ///< Texture containing the pixels of the glyphs - std::vector rows; ///< List containing the position of all the existing rows - Uint32 fontInternalId{ 0 }; // The font internal id - unsigned int nextRow; ///< Y position of the next new row in the texture + drawables; ///> Table mapping code points to their corresponding glyph drawables. + TexturePtr texture; ///< Texture containing the pixels of the glyphs + std::vector rows; ///< List containing the position of all the existing rows + Uint32 fontInternalId{ 0 }; // The font internal id + unsigned int nextRow; ///< Y position of the next new row in the texture const FontTrueType* font{ nullptr }; }; diff --git a/include/eepp/graphics/framebuffer.hpp b/include/eepp/graphics/framebuffer.hpp index b2613c4f3..93bf9fa11 100644 --- a/include/eepp/graphics/framebuffer.hpp +++ b/include/eepp/graphics/framebuffer.hpp @@ -62,7 +62,7 @@ class EE_API FrameBuffer { ** The frame buffer must be unbinded before any rendering is done outside the frame buffer. ** For example MyFrameBufferPtr->getTexture()->Draw(0,0); */ - Texture* getTexture() const; + const TexturePtr& getTexture() const; /** @brief Sets the frame buffer clear color. */ void setClearColor( const ColorAf& color ); @@ -123,7 +123,7 @@ class EE_API FrameBuffer { bool mHasStencilBuffer{ false }; bool mAdjustCurrentClipping{ true }; bool mNeedsToRestoreScissorsClipping{ false }; - Texture* mTexture{ nullptr }; + TexturePtr mTexture; ColorAf mClearColor; View mView; float mProjMat[16]; diff --git a/include/eepp/graphics/glyphdrawable.hpp b/include/eepp/graphics/glyphdrawable.hpp index 6c72bcbce..c399fa893 100644 --- a/include/eepp/graphics/glyphdrawable.hpp +++ b/include/eepp/graphics/glyphdrawable.hpp @@ -11,7 +11,7 @@ class VertexBuffer; class EE_API GlyphDrawable : public DrawableResource { public: - static GlyphDrawable* New( Texture* texture, const Rect& srcRect, const Sizef& destSize = {}, + static GlyphDrawable* New( TexturePtr texture, const Rect& srcRect, const Sizef& destSize = {}, const std::string& resourceName = "" ); enum class DrawMode { @@ -21,7 +21,7 @@ class EE_API GlyphDrawable : public DrawableResource { ///< italic skew }; - GlyphDrawable( Texture* texture, const Rect& srcRect, const Sizef& destSize = {}, + GlyphDrawable( TexturePtr texture, const Rect& srcRect, const Sizef& destSize = {}, const std::string& resourceName = "" ); virtual void draw(); @@ -36,7 +36,7 @@ class EE_API GlyphDrawable : public DrawableResource { virtual bool isStateful(); /** @return The texture instance used by the GlyphDrawable. */ - Texture* getTexture(); + const TexturePtr& getTexture() const; /** @return The Texture sector that represents the GlyphDrawable */ const Rectf& getSrcRect() const; @@ -69,7 +69,7 @@ class EE_API GlyphDrawable : public DrawableResource { void setAdvance( Float advance ); protected: - Texture* mTexture; + TexturePtr mTexture; Rectf mSrcRect; Sizef mDestSize; Float mPixelDensity; diff --git a/include/eepp/graphics/ninepatch.hpp b/include/eepp/graphics/ninepatch.hpp index dd2ba9915..e81f0af1d 100644 --- a/include/eepp/graphics/ninepatch.hpp +++ b/include/eepp/graphics/ninepatch.hpp @@ -25,13 +25,13 @@ class EE_API NinePatch : public DrawableResource { static NinePatch* New( ResourceId textureId, int left, int top, int right, int bottom, const Float& pixelDensity = 1, const std::string& name = "" ); - static NinePatch* New( Texture* tex, int left, int top, int right, int bottom, + static NinePatch* New( TexturePtr tex, int left, int top, int right, int bottom, const Float& pixelDensity = 1, const std::string& name = "" ); static NinePatch* New( TextureRegion* textureRegion, int left, int top, int right, int bottom, const std::string& name = "" ); - NinePatch( Texture* tex, int left, int top, int right, int bottom, + NinePatch( TexturePtr tex, int left, int top, int right, int bottom, const Float& pixelDensity = 1, const std::string& name = "" ); NinePatch( TextureRegion* textureRegion, int left, int top, int right, int bottom, @@ -61,7 +61,7 @@ class EE_API NinePatch : public DrawableResource { Sizef mDestSize; Float mPixelDensity; - void createFromTexture( Texture* tex, int left, int top, int right, int bottom ); + void createFromTexture( const TexturePtr& tex, int left, int top, int right, int bottom ); virtual void onAlphaChange(); diff --git a/include/eepp/graphics/particlesystem.hpp b/include/eepp/graphics/particlesystem.hpp index 3d9dea7ed..9995cd1b5 100644 --- a/include/eepp/graphics/particlesystem.hpp +++ b/include/eepp/graphics/particlesystem.hpp @@ -12,6 +12,7 @@ using namespace EE::System; namespace EE { namespace Graphics { class Texture; +using TexturePtr = ResourcePtr; /** @enum EE::Graphics::ParticleEffect Predefined effects for the particle system. Use Callback when * wan't to create a new effect, o set the parameters using NoFx, but it's much more limited. */ @@ -157,7 +158,7 @@ class EE_API ParticleSystem { private: Particle* mParticle; Uint32 mPCount; - const Texture* mTexture; + TexturePtr mTexture; Uint32 mPLeft; Uint32 mLoops; diff --git a/include/eepp/graphics/sprite.hpp b/include/eepp/graphics/sprite.hpp index 9ce4d4f95..38dc9232d 100644 --- a/include/eepp/graphics/sprite.hpp +++ b/include/eepp/graphics/sprite.hpp @@ -211,7 +211,7 @@ class EE_API Sprite : public Drawable { * @param TexSector The texture sector to be rendered ( default all the texture ) * @return True if success */ - bool createStatic( Texture* tex, const Sizef& DestSize = Sizef( 0, 0 ), + bool createStatic( TexturePtr tex, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); @@ -238,7 +238,7 @@ class EE_API Sprite : public Drawable { * @param TexSector The texture sector to be rendered ( default all the texture ) * @return The frame position or 0 if fails */ - unsigned int addFrame( Texture* tex, const Sizef& DestSize = Sizef( 0, 0 ), + unsigned int addFrame( TexturePtr tex, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); @@ -269,7 +269,7 @@ class EE_API Sprite : public Drawable { * @param TexSector The texture sector to be rendered ( default all the texture ) * @return True if success */ - bool addSubFrame( Texture* tex, const unsigned int& NumFrame, const unsigned int& NumSubFrame, + bool addSubFrame( TexturePtr tex, const unsigned int& NumFrame, const unsigned int& NumSubFrame, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); @@ -395,10 +395,6 @@ class EE_API Sprite : public Drawable { bool isTextureRegionOwner() const; - Sprite& setAsTextureOwner( bool set ); - - bool isTextureOwner() const; - protected: enum SpriteFlags { SPRITE_FLAG_AUTO_ANIM = ( 1 << 0 ), @@ -406,7 +402,6 @@ class EE_API Sprite : public Drawable { SPRITE_FLAG_ANIM_PAUSED = ( 1 << 2 ), SPRITE_FLAG_ANIM_TO_FRAME_AND_STOP = ( 1 << 3 ), SPRITE_FLAG_EVENTS_ENABLED = ( 1 << 4 ), - SPRITE_FLAG_TEXTURE_OWNER = ( 1 << 5 ), SPRITE_FLAG_TEXTURE_REGION_OWNER = ( 1 << 6 ), }; diff --git a/include/eepp/graphics/texture.hpp b/include/eepp/graphics/texture.hpp index cd2f3bd1c..676a094b6 100644 --- a/include/eepp/graphics/texture.hpp +++ b/include/eepp/graphics/texture.hpp @@ -34,7 +34,7 @@ class EE_API Texture : public DrawableResource, public Image, private NonCopyabl static Uint32 getMaximumSize(); /* @return an array of Textures and the delay of the first frame */ - static std::pair, int> loadGif( IOStream& stream ); + static std::pair, int> loadGif( IOStream& stream ); /** Set the OpenGL Texture Id (texture handle) */ void setHandle( const int& texture ) { mTexture = texture; } diff --git a/include/eepp/graphics/textureatlas.hpp b/include/eepp/graphics/textureatlas.hpp index cda6c8002..2ca77e2b3 100644 --- a/include/eepp/graphics/textureatlas.hpp +++ b/include/eepp/graphics/textureatlas.hpp @@ -63,14 +63,14 @@ class EE_API TextureAtlas : public ResourceManager { * @param tex The texture * @param Name The texture name ( if any ) */ - TextureRegion* add( Texture* tex, const std::string& Name = "" ); + TextureRegion* add( TexturePtr tex, const std::string& Name = "" ); /** Creates and add to the texture atlas a TextureRegion of the indicated part of the texture. * @param tex The texture * @param SrcRect The texture part that will be used as the TextureRegion. * @param Name The texture name ( if any ) */ - TextureRegion* add( Texture* tex, const Rect& SrcRect, const std::string& Name = "" ); + TextureRegion* add( TexturePtr tex, const Rect& SrcRect, const std::string& Name = "" ); /** Creates and add to the texture atlas a TextureRegion of the indicated part of the texture. * @param tex The texture @@ -78,7 +78,7 @@ class EE_API TextureAtlas : public ResourceManager { * @param DestSize The destination size that the TextureRegion will have when rendered. * @param Name The texture name ( if any ) */ - TextureRegion* add( Texture* tex, const Rect& SrcRect, const Sizef& DestSize, + TextureRegion* add( TexturePtr tex, const Rect& SrcRect, const Sizef& DestSize, const std::string& Name = "" ); /** Creates and add to the texture atlas a TextureRegion of the indicated part of the texture. @@ -89,7 +89,7 @@ class EE_API TextureAtlas : public ResourceManager { *used. * @param Name The texture name ( if any ) */ - TextureRegion* add( Texture* tex, const Rect& SrcRect, const Sizef& DestSize, + TextureRegion* add( TexturePtr tex, const Rect& SrcRect, const Sizef& DestSize, const Vector2i& Offset, const std::string& Name = "" ); /** @return The texture atlas name. */ @@ -119,7 +119,7 @@ class EE_API TextureAtlas : public ResourceManager { * linked to a texture. \n The Global Texture Atlas for example doesn't have any texture linked * to it. */ - Texture* getTexture( const Uint32& texnum = 0 ) const; + const TexturePtr& getTexture( const Uint32& texnum = 0 ) const; /** @return The number of textures linked to the texture atlas. */ Uint32 getTexturesCount(); @@ -130,9 +130,9 @@ class EE_API TextureAtlas : public ResourceManager { std::string mName; String::HashType mId; std::string mPath; - std::vector mTextures; + std::vector mTextures; - void setTextures( std::vector textures ); + void setTextures( std::vector textures ); }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/textureatlasloader.hpp b/include/eepp/graphics/textureatlasloader.hpp index 7a4dfd97e..451418fca 100644 --- a/include/eepp/graphics/textureatlasloader.hpp +++ b/include/eepp/graphics/textureatlasloader.hpp @@ -146,7 +146,7 @@ class EE_API TextureAtlasLoader { * 0 to GetTexturesLoadedCount(). Usually a texture atlas corresponds to only one texture, so * the texture index is 0. */ - Texture* getTexture( const Uint32& texnum = 0 ) const; + const TexturePtr& getTexture( const Uint32& texnum = 0 ) const; /** @return The number of textures linked to the texture atlas. */ Uint32 getTexturesLoadedCount(); @@ -170,11 +170,12 @@ class EE_API TextureAtlasLoader { std::atomic mIsLoading; TextureAtlas* mTextureAtlas; GLLoadCallback mLoadCallback; - std::vector mTexturesLoaded; + std::vector mTexturesLoaded; struct sTempTexAtlas { sTextureHdr Texture; std::vector TextureRegions; + TexturePtr LoadedTexture; }; sTextureAtlasHdr mTexGrHdr; diff --git a/include/eepp/graphics/texturefactory.hpp b/include/eepp/graphics/texturefactory.hpp index f2ba74cd5..71a9874c3 100644 --- a/include/eepp/graphics/texturefactory.hpp +++ b/include/eepp/graphics/texturefactory.hpp @@ -39,7 +39,7 @@ class EE_API TextureFactory : protected Mutex { * @param Filename A filename to recognize the texture. * @return The created texture */ - Texture* createEmptyTexture( + TexturePtr createEmptyTexture( const unsigned int& Width, const unsigned int& Height, const unsigned int& Channels = 4, const Color& DefaultColor = Color( 0, 0, 0, 255 ), const bool& Mipmap = false, const Texture::ClampMode& ClampMode = Texture::ClampMode::ClampToEdge, @@ -60,12 +60,13 @@ class EE_API TextureFactory : protected Mutex { * outside the texture factory ). * @return The texture loaded or null if error */ - Texture* loadFromPixels( const unsigned char* Pixels, const unsigned int& Width, - const unsigned int& Height, const unsigned int& Channels, - const bool& Mipmap = false, - const Texture::ClampMode& ClampMode = Texture::ClampMode::ClampToEdge, - const bool& CompressTexture = false, const bool& KeepLocalCopy = false, - const std::string& FileName = std::string( "" ) ); + TexturePtr + loadFromPixels( const unsigned char* Pixels, const unsigned int& Width, + const unsigned int& Height, const unsigned int& Channels, + const bool& Mipmap = false, + const Texture::ClampMode& ClampMode = Texture::ClampMode::ClampToEdge, + const bool& CompressTexture = false, const bool& KeepLocalCopy = false, + const std::string& FileName = std::string( "" ) ); /** Load a texture from Pack file * @param Pack Pointer to the pack instance @@ -79,7 +80,7 @@ class EE_API TextureFactory : protected Mutex { * the image. * @return The texture loaded or null if error */ - Texture* loadFromPack( + TexturePtr loadFromPack( Pack* Pack, const std::string& FilePackPath, const bool& Mipmap = false, const Texture::ClampMode& ClampMode = Texture::ClampMode::ClampToEdge, const bool& CompressTexture = false, const bool& KeepLocalCopy = false, @@ -97,7 +98,7 @@ class EE_API TextureFactory : protected Mutex { * the image. * @return The texture loaded or null if error */ - Texture* loadFromMemory( + TexturePtr loadFromMemory( const unsigned char* ImagePtr, const unsigned int& Size, const bool& Mipmap = false, const Texture::ClampMode& ClampMode = Texture::ClampMode::ClampToEdge, const bool& CompressTexture = false, const bool& KeepLocalCopy = false, @@ -114,7 +115,7 @@ class EE_API TextureFactory : protected Mutex { * the image. * @return The texture loaded or null if error */ - Texture* loadFromStream( + TexturePtr loadFromStream( IOStream& Stream, const bool& Mipmap = false, const Texture::ClampMode& ClampMode = Texture::ClampMode::ClampToEdge, const bool& CompressTexture = false, const bool& KeepLocalCopy = false, @@ -131,24 +132,12 @@ class EE_API TextureFactory : protected Mutex { * the image. * @return The texture loaded or null if error */ - Texture* loadFromFile( + TexturePtr loadFromFile( const std::string& Filepath, const bool& Mipmap = false, const Texture::ClampMode& ClampMode = Texture::ClampMode::ClampToEdge, const bool& CompressTexture = false, const bool& KeepLocalCopy = false, const Image::FormatConfiguration& imageformatConfiguration = Image::FormatConfiguration() ); - /** Removes and unloads the texture identified by @p textureId. - * @param textureId The process-wide texture identity. - * @return True if was removed - */ - bool remove( ResourceId textureId ); - - /** Removes and Unload the Texture - * @param texture The texture pointer - * @return True if was removed - */ - bool remove( Texture* texture ); - /** Binds the texture identity indicated. This is useful if you are rendering a texture * outside this class. * @param textureId The process-wide texture identity. @@ -186,9 +175,6 @@ class EE_API TextureFactory : protected Mutex { /** Returns the number of textures loaded */ Uint32 getTextureCount(); - /** @return All the active textures */ - std::vector getTextures(); - /** @return A non-owning diagnostic snapshot of every currently live texture. */ TextureRegistrySnapshot snapshotTextures(); @@ -218,11 +204,8 @@ class EE_API TextureFactory : protected Mutex { /** Determines whether the texture identity exists in the factory. */ bool existsId( ResourceId textureId ); - /** Determines whether the texture is retained by the factory. */ - bool exists( const Texture* tex ); - /** @return The texture matching @p textureId, or null if it is not factory-retained. */ - Texture* getTexture( ResourceId textureId ); + TexturePtr getTexture( ResourceId textureId ); /** @return The memory used by the textures (in bytes) */ unsigned int getTextureMemorySize(); @@ -243,24 +226,24 @@ class EE_API TextureFactory : protected Mutex { * @param MemSize The size of the texture in memory ( just if you need to specify the real size * in memory, just useful to calculate the total texture memory ). */ - Texture* pushTexture( const std::string& Filepath, const Uint32& textureHandle, - const unsigned int& Width, const unsigned int& Height, - const unsigned int& ImgWidth, const unsigned int& ImgHeight, - const bool& Mipmap, const unsigned int& Channels, - const Texture::ClampMode& ClampMode, const bool& CompressTexture, - const bool& LocalCopy = false, const Uint32& MemSize = 0 ); + TexturePtr pushTexture( const std::string& Filepath, const Uint32& textureHandle, + const unsigned int& Width, const unsigned int& Height, + const unsigned int& ImgWidth, const unsigned int& ImgHeight, + const bool& Mipmap, const unsigned int& Channels, + const Texture::ClampMode& ClampMode, const bool& CompressTexture, + const bool& LocalCopy = false, const Uint32& MemSize = 0 ); /** Return a texture by it file path name * @param Name File path name * @return The texture, NULL if not exists. */ - Texture* getByName( const std::string& Name ); + TexturePtr getByName( const std::string& Name ); /** Return a texture by it hash path name * @param Hash The file path hash * @return The texture, NULL if not exists */ - Texture* getByHash( const String::HashType& hash ); + TexturePtr getByHash( const String::HashType& hash ); ~TextureFactory(); @@ -268,6 +251,7 @@ class EE_API TextureFactory : protected Mutex { protected: friend class Texture; + friend class TextureLoader; TextureFactory(); @@ -295,6 +279,8 @@ class EE_API TextureFactory : protected Mutex { void resetTextureBinding( const Texture* texture ); + bool releaseRetainedTexture( ResourceId textureId ); + void queueReleasedTexture( Texture* texture ); void diagnoseLiveTexturesAtShutdown(); diff --git a/include/eepp/graphics/textureloader.hpp b/include/eepp/graphics/textureloader.hpp index 2f5706ba6..00d59e2ef 100644 --- a/include/eepp/graphics/textureloader.hpp +++ b/include/eepp/graphics/textureloader.hpp @@ -89,9 +89,9 @@ class EE_API TextureLoader { * is done. */ void setColorKey( RGB Color ); - /** @brief Releases the texture loaded ( if was already loaded ), it will destroy the texture - * from memory. */ - void unload(); + /** Clears the loader state and releases its texture handle. Other texture owners remain valid. + */ + void reset(); /** @return The file path to the texture ( if any ) */ const std::string& getFilepath() const; @@ -100,7 +100,7 @@ class EE_API TextureLoader { ResourceId getId() const; /** @return The texture instance ( if it was loaded ). */ - Texture* getTexture() const; + const TexturePtr& getTexture() const; Image::FormatConfiguration getFormatConfiguration() const; @@ -112,7 +112,7 @@ class EE_API TextureLoader { protected: Uint32 mLoadType{ 0 }; // From memory, from path, from pack Uint8* mPixels{ nullptr }; // Texture Info - Texture* mTexture{ nullptr }; + TexturePtr mTexture; Int32 mImgWidth{ 0 }; Int32 mImgHeight{ 0 }; @@ -133,8 +133,6 @@ class EE_API TextureLoader { RGB* mColorKey{ nullptr }; Image::FormatConfiguration mFormatConfiguration; - void reset(); - private: bool mLoaded{ false }; bool mTexLoaded{ false }; diff --git a/include/eepp/graphics/textureregion.hpp b/include/eepp/graphics/textureregion.hpp index 177a7e892..7ac5f3941 100644 --- a/include/eepp/graphics/textureregion.hpp +++ b/include/eepp/graphics/textureregion.hpp @@ -24,14 +24,14 @@ class EE_API TextureRegion : public DrawableResource { static TextureRegion* New( ResourceId textureId, const Rect& srcRect, const Sizef& destSize, const Vector2i& offset, const std::string& name = "" ); - static TextureRegion* New( Texture* tex, const std::string& name = "" ); + static TextureRegion* New( TexturePtr tex, const std::string& name = "" ); - static TextureRegion* New( Texture* tex, const Rect& srcRect, const std::string& name = "" ); + static TextureRegion* New( TexturePtr tex, const Rect& srcRect, const std::string& name = "" ); - static TextureRegion* New( Texture* tex, const Rect& srcRect, const Sizef& destSize, + static TextureRegion* New( TexturePtr tex, const Rect& srcRect, const Sizef& destSize, const std::string& name = "" ); - static TextureRegion* New( Texture* tex, const Rect& srcRect, const Sizef& destSize, + static TextureRegion* New( TexturePtr tex, const Rect& srcRect, const Sizef& destSize, const Vector2i& offset, const std::string& name = "" ); /** Creates an empty TextureRegion */ @@ -41,14 +41,14 @@ class EE_API TextureRegion : public DrawableResource { * @param tex The texture * @param name The texture name ( if any ) */ - TextureRegion( Texture* tex, const std::string& name = "" ); + TextureRegion( TexturePtr tex, const std::string& name = "" ); /** Creates a TextureRegion of the indicated part of the texture. * @param tex The texture * @param srcRect The texture part that will be used as the TextureRegion. * @param name The texture name ( if any ) */ - TextureRegion( Texture* tex, const Rect& srcRect, const std::string& name = "" ); + TextureRegion( TexturePtr tex, const Rect& srcRect, const std::string& name = "" ); /** Creates a TextureRegion of the indicated part of the texture. * @param tex The texture @@ -56,7 +56,7 @@ class EE_API TextureRegion : public DrawableResource { * @param destSize The destination size that the TextureRegion will have when rendered. * @param name The texture name ( if any ) */ - TextureRegion( Texture* tex, const Rect& srcRect, const Sizef& destSize, + TextureRegion( TexturePtr tex, const Rect& srcRect, const Sizef& destSize, const std::string& name = "" ); /** Creates a TextureRegion of the indicated part of the texture. @@ -67,8 +67,8 @@ class EE_API TextureRegion : public DrawableResource { *used. * @param name The texture name ( if any ) */ - TextureRegion( Texture* tex, const Rect& srcRect, const Sizef& destSize, const Vector2i& offset, - const std::string& name = "" ); + TextureRegion( TexturePtr tex, const Rect& srcRect, const Sizef& destSize, + const Vector2i& offset, const std::string& name = "" ); virtual ~TextureRegion(); @@ -76,7 +76,7 @@ class EE_API TextureRegion : public DrawableResource { void setTextureId( ResourceId textureId ); /** Set the Texture that holds the TextureRegion. */ - void setTexture( Texture* texture ); + void setTexture( TexturePtr texture ); /** @return The Texture sector that represents the TextureRegion */ const Rect& getSrcRect() const; @@ -127,7 +127,7 @@ class EE_API TextureRegion : public DrawableResource { virtual bool isStateful() { return false; } /** @return The texture instance used by the TextureRegion. */ - Graphics::Texture* getTexture(); + const TexturePtr& getTexture() const; /** Replaces a color in the TextureRegion ( needs Lock() ) */ void replaceColor( Color ColorKey, Color NewColor ); @@ -204,7 +204,7 @@ class EE_API TextureRegion : public DrawableResource { protected: Uint8* mPixels; Uint8* mAlphaMask; - Graphics::Texture* mTexture; + TexturePtr mTexture; Rect mSrcRect; Sizef mOriDestSize; Sizef mDestSize; diff --git a/include/eepp/ui/tools/uicolorpicker.hpp b/include/eepp/ui/tools/uicolorpicker.hpp index 409757b65..4021eb7d3 100644 --- a/include/eepp/ui/tools/uicolorpicker.hpp +++ b/include/eepp/ui/tools/uicolorpicker.hpp @@ -78,9 +78,9 @@ class EE_API UIColorPicker { void windowClose( const Event* Event ); - Texture* createHueTexture( const Sizef& size ); + TexturePtr createHueTexture( const Sizef& size ); - Texture* createGridTexture(); + TexturePtr createGridTexture(); void updateColorPicker(); diff --git a/include/eepp/ui/uiicon.hpp b/include/eepp/ui/uiicon.hpp index f7f4c1821..f53fcb690 100644 --- a/include/eepp/ui/uiicon.hpp +++ b/include/eepp/ui/uiicon.hpp @@ -59,7 +59,7 @@ class EE_API UISVGIcon : public UIIcon { UISVGIcon( const std::string& name, const std::string& svgXML ); std::string mSVGXml; - mutable UnorderedMap mSVGs; + mutable UnorderedMap mSVGs; mutable Sizei mOriSize; mutable int mOriChannels{ 0 }; }; diff --git a/include/eepp/ui/uiimage.hpp b/include/eepp/ui/uiimage.hpp index 37569f65a..aa926d993 100644 --- a/include/eepp/ui/uiimage.hpp +++ b/include/eepp/ui/uiimage.hpp @@ -28,6 +28,8 @@ class EE_API UIImage : public UIWidget { UIImage* setDrawable( Drawable* drawable, bool ownIt = false ); + UIImage* setDrawable( TexturePtr texture ); + const Color& getColor() const; UIImage* setColor( const Color& col ); @@ -54,6 +56,7 @@ class EE_API UIImage : public UIWidget { protected: UIScaleType mScaleType; Drawable* mDrawable; + TexturePtr mTexture; Color mColor; Vector2f mAlignOffset; Vector2f mDestSize; diff --git a/include/eepp/ui/uinodedrawable.hpp b/include/eepp/ui/uinodedrawable.hpp index c9b7f47c8..f129580ff 100644 --- a/include/eepp/ui/uinodedrawable.hpp +++ b/include/eepp/ui/uinodedrawable.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,8 @@ class EE_API UINodeDrawable : public Drawable { void setDrawable( Drawable* drawable, const bool& ownIt ); + void setDrawable( TexturePtr texture ); + void setDrawable( const std::string& drawableRef ); void setOffset( const Vector2f& offset ); @@ -131,6 +134,7 @@ class EE_API UINodeDrawable : public Drawable { bool mOwnsDrawable{ false }; bool mColorWasSet{ false }; Drawable* mDrawable; + TexturePtr mTexture; std::string mDrawableRef; Uint32 mResourceChangeCbId; RepeatX mRepeatX{ RepeatX::NoRepeat }; diff --git a/include/eepp/window/cursor.hpp b/include/eepp/window/cursor.hpp index dac650a5a..f8b99cb06 100644 --- a/include/eepp/window/cursor.hpp +++ b/include/eepp/window/cursor.hpp @@ -78,6 +78,7 @@ class EE_API Cursor { Vector2i mHotSpot; EE::Window::Window* mWindow; + // The texture pixels are copied synchronously; Cursor does not retain this borrow. Cursor( Texture* tex, const Vector2i& hotspot, const std::string& getName, EE::Window::Window* window ); diff --git a/src/eepp/graphics/batchrenderer.cpp b/src/eepp/graphics/batchrenderer.cpp index 781c98d96..0928d87eb 100644 --- a/src/eepp/graphics/batchrenderer.cpp +++ b/src/eepp/graphics/batchrenderer.cpp @@ -53,16 +53,26 @@ void BatchRenderer::discard() { mNumVertex = 0; mTVertex = nullptr; mTexture = nullptr; + mTextureOwner.reset(); } void BatchRenderer::setTexture( const Texture* texture, Texture::CoordinateType coordinateType ) { - if ( mTexture != texture || mCoordinateType != coordinateType ) + if ( mTexture != texture || mCoordinateType != coordinateType ) { flush(); + mTextureOwner.reset(); + } mTexture = texture; mCoordinateType = coordinateType; } +void BatchRenderer::setTexture( const TexturePtr& texture, + Texture::CoordinateType coordinateType ) { + setTexture( texture.get(), coordinateType ); + if ( mTextureOwner != texture ) + mTextureOwner = texture; +} + void BatchRenderer::setBlendMode( const BlendMode& blend ) { if ( blend != mBlend ) flush(); @@ -94,8 +104,10 @@ void BatchRenderer::setDrawMode( const PrimitiveType& Mode, const bool& Force ) } void BatchRenderer::flush() { - if ( mNumVertex == 0 ) + if ( mNumVertex == 0 ) { + mTextureOwner.reset(); return; + } if ( GlobalBatchRenderer::instance() != this ) GlobalBatchRenderer::instance()->draw(); @@ -163,6 +175,8 @@ void BatchRenderer::flush() { GLi->enable( GL_TEXTURE_2D ); GLi->enableClientState( GL_TEXTURE_COORD_ARRAY ); } + + mTextureOwner.reset(); } void BatchRenderer::batchQuad( const Float& x, const Float& y, const Float& width, diff --git a/src/eepp/graphics/drawablesearcher.cpp b/src/eepp/graphics/drawablesearcher.cpp index 7bb4ad7ba..3e84c3ef9 100644 --- a/src/eepp/graphics/drawablesearcher.cpp +++ b/src/eepp/graphics/drawablesearcher.cpp @@ -42,7 +42,7 @@ static Drawable* searchByNameInternal( const std::string& name ) { } if ( NULL == drawable ) { - drawable = TextureFactory::instance()->getByHash( id ); + drawable = TextureFactory::instance()->getByHash( id ).get(); } return drawable; @@ -50,7 +50,8 @@ static Drawable* searchByNameInternal( const std::string& name ) { static Drawable* parseDataURI( const std::string& name ) { auto hash = MD5::fromString( name ).toHexString(); - Drawable* drawable = TextureFactory::instance()->getByName( hash ); + TexturePtr texture = TextureFactory::instance()->getByName( hash ); + Drawable* drawable = texture.get(); std::string::size_type formatAndEncSep; if ( nullptr == drawable && ( formatAndEncSep = name.find_first_of( ',' ) ) != std::string::npos ) { @@ -72,7 +73,7 @@ static Drawable* parseDataURI( const std::string& name ) { } } - Texture* tex = nullptr; + TexturePtr tex; if ( !format.empty() && ( Image::isImageExtension( "." + format ) || format == "svg+xml" ) ) { Image::FormatConfiguration format; @@ -100,7 +101,7 @@ static Drawable* parseDataURI( const std::string& name ) { if ( tex ) { tex->setName( hash ); - drawable = tex; + drawable = tex.get(); } } return drawable; @@ -132,9 +133,9 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea drawable = TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ); } else if ( String::startsWith( name, "@image/" ) ) { - drawable = TextureFactory::instance()->getByName( name.substr( 7 ) ); + drawable = TextureFactory::instance()->getByName( name.substr( 7 ) ).get(); } else if ( String::startsWith( name, "@texture/" ) ) { - drawable = TextureFactory::instance()->getByName( name.substr( 9 ) ); + drawable = TextureFactory::instance()->getByName( name.substr( 9 ) ).get(); } else if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) { drawable = getSprite( name.substr( 8 ) ); } else if ( String::startsWith( name, "@drawable/" ) ) { @@ -156,17 +157,17 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea FileSystem::filePathRemoveProcessPath( filePath ); - drawable = TextureFactory::instance()->getByName( filePath ); + drawable = TextureFactory::instance()->getByName( filePath ).get(); if ( NULL == drawable ) { - Texture* tex = TextureFactory::instance()->loadFromFile( filePath ); + TexturePtr tex = TextureFactory::instance()->loadFromFile( filePath ); if ( tex ) - drawable = tex; + drawable = tex.get(); } } else if ( String::startsWith( name, "http://" ) || String::startsWith( name, "https://" ) ) { - Texture* texture = TextureFactory::instance()->getByName( name ); + TexturePtr texture = TextureFactory::instance()->getByName( name ); if ( NULL == texture && Engine::instance()->isSharedGLContextEnabled() ) { texture = TextureFactory::instance()->createEmptyTexture( @@ -194,7 +195,7 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea URI( name ), Seconds( 5 ), {}, headers ); } - drawable = texture; + drawable = texture.get(); } else if ( String::startsWith( name, "data:image/" ) ) { drawable = parseDataURI( name ); } else { @@ -212,7 +213,7 @@ Drawable* DrawableSearcher::searchById( const Uint32& id ) { Drawable* drawable = TextureAtlasManager::instance()->getTextureRegionById( id ); if ( NULL == drawable ) { - drawable = TextureFactory::instance()->getByHash( id ); + drawable = TextureFactory::instance()->getByHash( id ).get(); } if ( NULL == drawable && sPrintWarnings ) diff --git a/src/eepp/graphics/fontbmfont.cpp b/src/eepp/graphics/fontbmfont.cpp index 2763a8d03..40e503fc1 100644 --- a/src/eepp/graphics/fontbmfont.cpp +++ b/src/eepp/graphics/fontbmfont.cpp @@ -113,13 +113,13 @@ bool FontBMFont::loadFromStream( IOStream& stream ) { } } - Texture* tex = TF->loadFromPixels( rgbaImg.getPixelsPtr(), rgbaImg.getWidth(), - rgbaImg.getHeight(), rgbaImg.getChannels() ); + TexturePtr tex = TF->loadFromPixels( rgbaImg.getPixelsPtr(), rgbaImg.getWidth(), + rgbaImg.getHeight(), rgbaImg.getChannels() ); mPages[mFontSize].texture = tex; } else { - Texture* tex = TF->loadFromPixels( img.getPixelsPtr(), img.getWidth(), - img.getHeight(), img.getChannels() ); + TexturePtr tex = TF->loadFromPixels( img.getPixelsPtr(), img.getWidth(), + img.getHeight(), img.getChannels() ); mPages[mFontSize].texture = tex; } @@ -254,7 +254,7 @@ Float FontBMFont::getUnderlineThickness( unsigned int ) const { return 0.f; } -Texture* FontBMFont::getTexture( unsigned int ) const { +const TexturePtr& FontBMFont::getTexture( unsigned int ) const { return mPages[mFontSize].texture; } @@ -274,9 +274,6 @@ FontBMFont& FontBMFont::operator=( const FontBMFont& right ) { FontBMFont::Page::~Page() { for ( auto drawable : drawables ) eeDelete( drawable.second ); - - if ( NULL != texture && TextureFactory::existsSingleton() ) - TextureFactory::instance()->remove( texture->getTextureId() ); } }} // namespace EE::Graphics diff --git a/src/eepp/graphics/fontsprite.cpp b/src/eepp/graphics/fontsprite.cpp index fcd51031e..45d2e5337 100644 --- a/src/eepp/graphics/fontsprite.cpp +++ b/src/eepp/graphics/fontsprite.cpp @@ -136,7 +136,7 @@ bool FontSprite::loadFromStream( IOStream& stream, Color key, Uint32 firstChar, img.createMaskFromColor( Color::Fuchsia, 0 ); - Texture* texture = TextureFactory::instance()->loadFromPixels( + TexturePtr texture = TextureFactory::instance()->loadFromPixels( img.getPixelsPtr(), img.getWidth(), img.getHeight(), img.getChannels() ); mPages[mFontSize].texture = texture; if ( NULL != texture ) { @@ -232,7 +232,7 @@ Float FontSprite::getUnderlineThickness( unsigned int ) const { return 0.f; } -Texture* FontSprite::getTexture( unsigned int ) const { +const TexturePtr& FontSprite::getTexture( unsigned int ) const { return mPages[mFontSize].texture; } @@ -252,9 +252,6 @@ FontSprite& FontSprite::operator=( const FontSprite& right ) { FontSprite::Page::~Page() { for ( auto drawable : drawables ) eeDelete( drawable.second ); - - if ( NULL != texture && TextureFactory::existsSingleton() ) - TextureFactory::instance()->remove( texture->getTextureId() ); } }} // namespace EE::Graphics diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index 0518ab2a4..9f0d55703 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -1135,7 +1135,7 @@ Float FontTrueType::getUnderlineThickness( unsigned int characterSize ) const { } } -Texture* FontTrueType::getTexture( unsigned int characterSize ) const { +const TexturePtr& FontTrueType::getTexture( unsigned int characterSize ) const { return getPage( characterSize ).texture; } @@ -1621,7 +1621,7 @@ Rect FontTrueType::findGlyphRect( Page& page, unsigned int width, unsigned int h // Make the texture 2 times bigger Image newImage; newImage.create( textureWidth * 2, textureHeight * 2, 4 ); - newImage.copyImage( page.texture ); + newImage.copyImage( page.texture.get() ); page.texture->replace( &newImage ); } else { @@ -1958,7 +1958,7 @@ bool FontTrueType::hasColrGlyphs() const { FontTrueType::Page::Page( const Uint32 fontInternalId, const std::string& pageName, const FontTrueType* font ) : - texture( NULL ), fontInternalId( fontInternalId ), nextRow( 3 ), font( font ) { + texture(), fontInternalId( fontInternalId ), nextRow( 3 ), font( font ) { // Make sure that the texture is initialized by default Image image; image.create( 128, 128, 4 ); @@ -1979,9 +1979,6 @@ FontTrueType::Page::Page( const Uint32 fontInternalId, const std::string& pageNa FontTrueType::Page::~Page() { for ( auto drawable : drawables ) eeDelete( drawable.second ); - - if ( NULL != texture && TextureFactory::existsSingleton() ) - TextureFactory::instance()->remove( texture->getTextureId() ); } void FontTrueType::clearCache() { diff --git a/src/eepp/graphics/framebuffer.cpp b/src/eepp/graphics/framebuffer.cpp index 369f3e61d..ddd2642c6 100644 --- a/src/eepp/graphics/framebuffer.cpp +++ b/src/eepp/graphics/framebuffer.cpp @@ -32,7 +32,7 @@ FrameBuffer::FrameBuffer( EE::Window::Window* window ) : mHasColorBuffer( false ), mHasDepthBuffer( false ), mHasStencilBuffer( false ), - mTexture( NULL ), + mTexture(), mClearColor( 0, 0, 0, 0 ) { if ( NULL == mWindow ) { mWindow = Engine::instance()->getCurrentWindow(); @@ -42,13 +42,10 @@ FrameBuffer::FrameBuffer( EE::Window::Window* window ) : } FrameBuffer::~FrameBuffer() { - if ( mTexture && TextureFactory::existsSingleton() ) - TextureFactory::instance()->remove( mTexture ); - FrameBufferManager::instance()->remove( this ); } -Texture* FrameBuffer::getTexture() const { +const TexturePtr& FrameBuffer::getTexture() const { return mTexture; } diff --git a/src/eepp/graphics/framebufferfbo.cpp b/src/eepp/graphics/framebufferfbo.cpp index 3630877a5..dd8a7923f 100644 --- a/src/eepp/graphics/framebufferfbo.cpp +++ b/src/eepp/graphics/framebufferfbo.cpp @@ -163,11 +163,11 @@ bool FrameBufferFBO::create( const Uint32& Width, const Uint32& Height, bool Ste } else { if ( NULL == mTexture ) { - Texture* tex = TextureFactory::instance()->createEmptyTexture( Width, Height, channels, - Color::Transparent ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( + Width, Height, channels, Color::Transparent ); - if ( tex ) { - mTexture = tex; + if ( texture ) { + mTexture = std::move( texture ); } else { Log::error( "FrameBufferFBO::create: failed to create texture" ); return false; diff --git a/src/eepp/graphics/glyphdrawable.cpp b/src/eepp/graphics/glyphdrawable.cpp index d5fd2eb79..f1d189749 100644 --- a/src/eepp/graphics/glyphdrawable.cpp +++ b/src/eepp/graphics/glyphdrawable.cpp @@ -5,15 +5,15 @@ namespace EE { namespace Graphics { -GlyphDrawable* GlyphDrawable::New( Texture* texture, const Rect& srcRect, const Sizef& destSize, +GlyphDrawable* GlyphDrawable::New( TexturePtr texture, const Rect& srcRect, const Sizef& destSize, const std::string& resourceName ) { - return eeNew( GlyphDrawable, ( texture, srcRect, destSize, resourceName ) ); + return eeNew( GlyphDrawable, ( std::move( texture ), srcRect, destSize, resourceName ) ); } -GlyphDrawable::GlyphDrawable( Texture* texture, const Rect& srcRect, const Sizef& destSize, +GlyphDrawable::GlyphDrawable( TexturePtr texture, const Rect& srcRect, const Sizef& destSize, const std::string& resourceName ) : DrawableResource( Drawable::GLYPH, resourceName ), - mTexture( texture ), + mTexture( std::move( texture ) ), mSrcRect( srcRect.asFloat() ), mDestSize( destSize ), mAdvance( destSize.getWidth() ) { @@ -80,7 +80,7 @@ bool GlyphDrawable::isStateful() { return false; } -Texture* GlyphDrawable::getTexture() { +const TexturePtr& GlyphDrawable::getTexture() const { return mTexture; } diff --git a/src/eepp/graphics/ninepatch.cpp b/src/eepp/graphics/ninepatch.cpp index e0136e7a7..6b1e511b4 100644 --- a/src/eepp/graphics/ninepatch.cpp +++ b/src/eepp/graphics/ninepatch.cpp @@ -10,9 +10,9 @@ NinePatch* NinePatch::New( ResourceId textureId, int left, int top, int right, i right, bottom, pixelDensity, name ) ); } -NinePatch* NinePatch::New( Texture* tex, int left, int top, int right, int bottom, +NinePatch* NinePatch::New( TexturePtr tex, int left, int top, int right, int bottom, const Float& pixelDensity, const std::string& name ) { - return eeNew( NinePatch, ( tex, left, top, right, bottom, pixelDensity, name ) ); + return eeNew( NinePatch, ( std::move( tex ), left, top, right, bottom, pixelDensity, name ) ); } NinePatch* NinePatch::New( TextureRegion* textureRegion, int left, int top, int right, int bottom, @@ -20,7 +20,7 @@ NinePatch* NinePatch::New( TextureRegion* textureRegion, int left, int top, int return eeNew( NinePatch, ( textureRegion, left, top, right, bottom, name ) ); } -NinePatch::NinePatch( Texture* tex, int left, int top, int right, int bottom, +NinePatch::NinePatch( TexturePtr tex, int left, int top, int right, int bottom, const Float& pixelDensity, const std::string& name ) : DrawableResource( Drawable::NINEPATCH, name ), mRect( left, top, right, bottom ), @@ -43,16 +43,14 @@ NinePatch::NinePatch( TextureRegion* textureRegion, int left, int top, int right for ( Int32 i = 0; i < SideCount; i++ ) mDrawable[i] = NULL; - Texture* tex; - - if ( NULL != textureRegion && ( tex = textureRegion->getTexture() ) != NULL ) { + if ( NULL != textureRegion && textureRegion->getTexture() != NULL ) { mPixelDensity = textureRegion->getPixelDensity(); Rectf r( textureRegion->getSrcRect().asFloat() ); mSize = r.getSize(); - createFromTexture( tex, left, top, right, bottom ); + createFromTexture( textureRegion->getTexture(), left, top, right, bottom ); for ( int i = 0; i < SideCount; i++ ) { TextureRegion* side = static_cast( mDrawable[i] ); @@ -121,7 +119,8 @@ TextureRegion* NinePatch::getTextureRegion( const int& side ) { return NULL; } -void NinePatch::createFromTexture( Texture* tex, int left, int top, int right, int bottom ) { +void NinePatch::createFromTexture( const TexturePtr& tex, int left, int top, int right, + int bottom ) { Rect r; r = Rect( 0, top, left, mSize.getHeight() - bottom ); mDrawable[Left] = TextureRegion::New( tex, r, r.getSize().asFloat() ); diff --git a/src/eepp/graphics/particlesystem.cpp b/src/eepp/graphics/particlesystem.cpp index c85da4698..7f531da6b 100644 --- a/src/eepp/graphics/particlesystem.cpp +++ b/src/eepp/graphics/particlesystem.cpp @@ -14,7 +14,7 @@ namespace EE { namespace Graphics { ParticleSystem::ParticleSystem() : mParticle( NULL ), mPCount( 0 ), - mTexture( 0 ), + mTexture(), mPLeft( 0 ), mLoops( 0 ), mEffect( ParticleEffect::Nofx ), @@ -324,7 +324,7 @@ void ParticleSystem::draw() { if ( mPointsSup ) { if ( NULL != mTexture ) { - const_cast( mTexture )->bind(); + mTexture->bind(); } else { GLi->disable( GL_TEXTURE_2D ); GLi->disableClientState( GL_TEXTURE_COORD_ARRAY ); diff --git a/src/eepp/graphics/sprite.cpp b/src/eepp/graphics/sprite.cpp index a073343fd..9517f2ad5 100644 --- a/src/eepp/graphics/sprite.cpp +++ b/src/eepp/graphics/sprite.cpp @@ -37,7 +37,6 @@ Sprite* Sprite::fromGif( IOStream& stream ) { sprite->setAnimationSpeed( 1000.f / (float)delay ); sprite->setAsTextureRegionOwner( true ); - sprite->setAsTextureOwner( true ); return sprite; } @@ -140,21 +139,13 @@ void Sprite::clearFrame() { } void Sprite::cleanUpResources() { - if ( isTextureRegionOwner() || isTextureOwner() ) { + if ( isTextureRegionOwner() ) { size_t frames = getNumFrames(); for ( size_t i = 0; i < frames; i++ ) { for ( size_t f = 0; f < mFrames[i].Spr.size(); f++ ) { TextureRegion* region = mFrames[i].Spr[f]; - Texture* texture = region->getTexture(); - - if ( isTextureOwner() && texture && - TextureFactory::instance()->exists( texture ) ) { - TextureFactory::instance()->remove( texture ); - } - - if ( isTextureRegionOwner() ) - GlobalTextureAtlas::instance()->remove( region ); + GlobalTextureAtlas::instance()->remove( region ); } } } @@ -338,12 +329,12 @@ bool Sprite::createStatic( ResourceId textureId, const Sizef& DestSize, const Ve return false; } -bool Sprite::createStatic( Texture* tex, const Sizef& DestSize, const Vector2i& offset, +bool Sprite::createStatic( TexturePtr tex, const Sizef& DestSize, const Vector2i& offset, const Rect& TexSector ) { if ( tex ) { reset(); - addFrame( tex->getTextureId(), DestSize, offset, TexSector ); + addFrame( std::move( tex ), DestSize, offset, TexSector ); return true; } @@ -453,22 +444,39 @@ unsigned int Sprite::addFrame( ResourceId textureId, const Sizef& DestSize, cons return 0; } -unsigned int Sprite::addFrame( Texture* tex, const Sizef& DestSize, const Vector2i& offset, +unsigned int Sprite::addFrame( TexturePtr tex, const Sizef& DestSize, const Vector2i& offset, const Rect& TexSector ) { unsigned int id = framePos(); - if ( addSubFrame( tex, id, mCurrentSubFrame, DestSize, offset, TexSector ) ) + if ( addSubFrame( std::move( tex ), id, mCurrentSubFrame, DestSize, offset, TexSector ) ) return id; return 0; } -bool Sprite::addSubFrame( Texture* tex, const unsigned int& NumFrame, +bool Sprite::addSubFrame( TexturePtr tex, const unsigned int& NumFrame, const unsigned int& NumSubFrame, const Sizef& DestSize, const Vector2i& Offset, const Rect& TexSector ) { - if ( tex ) - return addSubFrame( tex->getTextureId(), NumFrame, NumSubFrame, DestSize, Offset, - TexSector ); + if ( tex ) { + TextureRegion* region = GlobalTextureAtlas::instance()->add( TextureRegion::New() ); + region->setTexture( std::move( tex ) ); + + if ( TexSector.Right > 0 && TexSector.Bottom > 0 ) + region->setSrcRect( TexSector ); + else + region->setSrcRect( Rect( 0, 0, (Int32)region->getTexture()->getImageWidth(), + (Int32)region->getTexture()->getImageHeight() ) ); + + Sizef destSize( DestSize ); + if ( destSize.x <= 0 ) + destSize.x = static_cast( region->getSrcRect().getWidth() ); + if ( destSize.y <= 0 ) + destSize.y = static_cast( region->getSrcRect().getHeight() ); + + region->setDestSize( destSize ); + region->setOffset( Offset ); + return addSubFrame( region, NumFrame, NumSubFrame ); + } return false; } @@ -478,32 +486,8 @@ bool Sprite::addSubFrame( ResourceId textureId, const unsigned int& NumFrame, if ( !TextureFactory::instance()->existsId( textureId ) ) return false; - Texture* Tex = TextureFactory::instance()->getTexture( textureId ); - TextureRegion* S = GlobalTextureAtlas::instance()->add( TextureRegion::New() ); - - S->setTextureId( textureId ); - - if ( TexSector.Right > 0 && TexSector.Bottom > 0 ) - S->setSrcRect( TexSector ); - else - S->setSrcRect( Rect( 0, 0, (Int32)Tex->getImageWidth(), (Int32)Tex->getImageHeight() ) ); - - Sizef destSize( DestSize ); - - if ( destSize.x <= 0 ) { - destSize.x = static_cast( S->getSrcRect().Right - S->getSrcRect().Left ); - } - - if ( destSize.y <= 0 ) { - destSize.y = static_cast( S->getSrcRect().Bottom - S->getSrcRect().Top ); - } - - S->setDestSize( destSize ); - S->setOffset( Offset ); - - addSubFrame( S, NumFrame, NumSubFrame ); - - return true; + return addSubFrame( TextureFactory::instance()->getTexture( textureId ), NumFrame, NumSubFrame, + DestSize, Offset, TexSector ); } void Sprite::update() { @@ -925,18 +909,6 @@ bool Sprite::isTextureRegionOwner() const { return mFlags & SPRITE_FLAG_TEXTURE_REGION_OWNER; } -Sprite& Sprite::setAsTextureOwner( bool set ) { - if ( set ) - mFlags |= SPRITE_FLAG_TEXTURE_OWNER; - else - mFlags &= ~SPRITE_FLAG_TEXTURE_OWNER; - return *this; -} - -bool Sprite::isTextureOwner() const { - return mFlags & SPRITE_FLAG_TEXTURE_OWNER; -} - void Sprite::setOrigin( const OriginPoint& origin ) { mOrigin = origin; } diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index 5e253647b..3cd21972e 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -356,7 +356,7 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo Sizef size{ 0, height }; size_t ssize = string.size(); BatchRenderer* BR = GlobalBatchRenderer::instance(); - Texture* fontTexture = font->getTexture( fontSize ); + const TexturePtr& fontTexture = font->getTexture( fontSize ); Float tabAlign = 0; GlyphDrawable* spaceGlyph = nullptr; GlyphDrawable* tabGlyph = nullptr; @@ -1763,7 +1763,7 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl if ( mColors.empty() ) return; - Texture* texture = mFontStyleConfig.Font->getTexture( mFontStyleConfig.CharacterSize ); + const TexturePtr& texture = mFontStyleConfig.Font->getTexture( mFontStyleConfig.CharacterSize ); if ( !texture ) return; texture->bind(); diff --git a/src/eepp/graphics/texture.cpp b/src/eepp/graphics/texture.cpp index af4cc6024..3304dde78 100644 --- a/src/eepp/graphics/texture.cpp +++ b/src/eepp/graphics/texture.cpp @@ -919,7 +919,7 @@ void Texture::draw( const Vector2f& position, const Sizef& size ) { size.y ); } -std::pair, int> Texture::loadGif( IOStream& stream ) { +std::pair, int> Texture::loadGif( IOStream& stream ) { stbi_io_callbacks callbacks; callbacks.read = &IOCb::read; callbacks.skip = &IOCb::skip; @@ -929,7 +929,7 @@ std::pair, int> Texture::loadGif( IOStream& stream ) { if ( type != STBI_gif ) return {}; stream.seek( 0 ); - std::vector gif; + std::vector gif; ScopedBuffer buf( stream.getSize() ); stream.read( (char*)buf.get(), buf.size() ); int width, height, frames, comp; diff --git a/src/eepp/graphics/textureatlas.cpp b/src/eepp/graphics/textureatlas.cpp index 09d4355e6..7605b3dd4 100644 --- a/src/eepp/graphics/textureatlas.cpp +++ b/src/eepp/graphics/textureatlas.cpp @@ -56,33 +56,33 @@ TextureRegion* TextureAtlas::add( ResourceId textureId, const Rect& SrcRect, con return add( TextureRegion::New( textureId, SrcRect, DestSize, Offset, Name ) ); } -TextureRegion* TextureAtlas::add( Texture* tex, const std::string& Name ) { - return add( TextureRegion::New( tex, Name ) ); +TextureRegion* TextureAtlas::add( TexturePtr tex, const std::string& Name ) { + return add( TextureRegion::New( std::move( tex ), Name ) ); } -TextureRegion* TextureAtlas::add( Texture* tex, const Rect& SrcRect, const std::string& Name ) { - return add( TextureRegion::New( tex, SrcRect, Name ) ); +TextureRegion* TextureAtlas::add( TexturePtr tex, const Rect& SrcRect, const std::string& Name ) { + return add( TextureRegion::New( std::move( tex ), SrcRect, Name ) ); } -TextureRegion* TextureAtlas::add( Texture* tex, const Rect& SrcRect, const Sizef& DestSize, +TextureRegion* TextureAtlas::add( TexturePtr tex, const Rect& SrcRect, const Sizef& DestSize, const std::string& Name ) { - return add( TextureRegion::New( tex, SrcRect, DestSize, Name ) ); + return add( TextureRegion::New( std::move( tex ), SrcRect, DestSize, Name ) ); } -TextureRegion* TextureAtlas::add( Texture* tex, const Rect& SrcRect, const Sizef& DestSize, +TextureRegion* TextureAtlas::add( TexturePtr tex, const Rect& SrcRect, const Sizef& DestSize, const Vector2i& Offset, const std::string& Name ) { - return add( TextureRegion::New( tex, SrcRect, DestSize, Offset, Name ) ); + return add( TextureRegion::New( std::move( tex ), SrcRect, DestSize, Offset, Name ) ); } Uint32 TextureAtlas::getCount() { return ResourceManager::getCount(); } -void TextureAtlas::setTextures( std::vector textures ) { - mTextures = textures; +void TextureAtlas::setTextures( std::vector textures ) { + mTextures = std::move( textures ); } -Texture* TextureAtlas::getTexture( const Uint32& texnum ) const { +const TexturePtr& TextureAtlas::getTexture( const Uint32& texnum ) const { eeASSERT( texnum < mTextures.size() ); return mTextures[texnum]; } diff --git a/src/eepp/graphics/textureatlasloader.cpp b/src/eepp/graphics/textureatlasloader.cpp index 7709f9201..03042a357 100644 --- a/src/eepp/graphics/textureatlasloader.cpp +++ b/src/eepp/graphics/textureatlasloader.cpp @@ -130,6 +130,8 @@ void TextureAtlasLoader::loadFromStream( IOStream& IOS ) { IOS.read( (char*)&mTexGrHdr, sizeof( sTextureAtlasHdr ) ); if ( mTexGrHdr.Magic == EE_TEXTURE_ATLAS_MAGIC ) { + // The complete entry vector is built before mRL starts. Each task below writes only the + // LoadedTexture member at its captured index, so worker execution cannot race a resize. for ( Uint32 i = 0; i < mTexGrHdr.TextureCount; i++ ) { sTextureHdr tTextureHdr; sTempTexAtlas tTexAtlas; @@ -143,16 +145,20 @@ void TextureAtlasLoader::loadFromStream( IOStream& IOS ) { std::string path( FileSystem::fileRemoveFileName( mTextureAtlasPath ) + name ); //! Checks if the texture is already loaded - Texture* tTex = TextureFactory::instance()->getByName( path ); + TexturePtr tTex = TextureFactory::instance()->getByName( path ); + tTexAtlas.LoadedTexture = tTex; if ( !mSkipResourceLoad && NULL == tTex ) { + const std::size_t textureIndex = mTempAtlass.size(); if ( NULL != mPack ) { - mRL.add( [this, path = std::move( path )] { - TextureFactory::instance()->loadFromPack( mPack, path ); + mRL.add( [this, textureIndex, path = std::move( path )] { + mTempAtlass[textureIndex].LoadedTexture = + TextureFactory::instance()->loadFromPack( mPack, path ); } ); } else { - mRL.add( [path = std::move( path )] { - TextureFactory::instance()->loadFromFile( path ); + mRL.add( [this, textureIndex, path = std::move( path )] { + mTempAtlass[textureIndex].LoadedTexture = + TextureFactory::instance()->loadFromFile( path ); } ); } } @@ -160,7 +166,7 @@ void TextureAtlasLoader::loadFromStream( IOStream& IOS ) { IOS.read( (char*)&tTexAtlas.TextureRegions[0], sizeof( sTextureRegionHdr ) * tTextureHdr.TextureRegionCount ); - mTempAtlass.push_back( tTexAtlas ); + mTempAtlass.push_back( std::move( tTexAtlas ) ); } } @@ -233,7 +239,7 @@ void TextureAtlasLoader::createTextureRegions() { FileSystem::filePathRemoveProcessPath( path ); - Texture* tTex = TextureFactory::instance()->getByName( path ); + TexturePtr tTex = tTexAtlas->LoadedTexture; if ( NULL != tTex ) mTexturesLoaded.push_back( tTex ); @@ -327,7 +333,7 @@ bool TextureAtlasLoader::isLoading() const { return mIsLoading.load(); } -Texture* TextureAtlasLoader::getTexture( const Uint32& texnum ) const { +const TexturePtr& TextureAtlasLoader::getTexture( const Uint32& texnum ) const { eeASSERT( texnum < mTexturesLoaded.size() ); return mTexturesLoaded[texnum]; } diff --git a/src/eepp/graphics/texturefactory.cpp b/src/eepp/graphics/texturefactory.cpp index 085ecf95c..db12b0c66 100644 --- a/src/eepp/graphics/texturefactory.cpp +++ b/src/eepp/graphics/texturefactory.cpp @@ -57,29 +57,27 @@ void TextureFactory::TextureDeleter::operator()( Texture* texture ) const noexce eeASSERTM( false, Texture_released_after_TextureFactory_destruction ); } -Texture* TextureFactory::createEmptyTexture( const unsigned int& Width, const unsigned int& Height, - const unsigned int& Channels, - const Color& DefaultColor, const bool& Mipmap, - const Texture::ClampMode& ClampMode, - const bool& CompressTexture, const bool& KeepLocalCopy, - const std::string& Filename ) { +TexturePtr TextureFactory::createEmptyTexture( + const unsigned int& Width, const unsigned int& Height, const unsigned int& Channels, + const Color& DefaultColor, const bool& Mipmap, const Texture::ClampMode& ClampMode, + const bool& CompressTexture, const bool& KeepLocalCopy, const std::string& Filename ) { Image TmpImg( Width, Height, Channels, DefaultColor ); return loadFromPixels( TmpImg.getPixelsPtr(), Width, Height, Channels, Mipmap, ClampMode, CompressTexture, KeepLocalCopy, Filename ); } -Texture* TextureFactory::loadFromPixels( const unsigned char* Pixels, const unsigned int& Width, - const unsigned int& Height, const unsigned int& Channels, - const bool& Mipmap, const Texture::ClampMode& ClampMode, - const bool& CompressTexture, const bool& KeepLocalCopy, - const std::string& FileName ) { +TexturePtr TextureFactory::loadFromPixels( const unsigned char* Pixels, const unsigned int& Width, + const unsigned int& Height, const unsigned int& Channels, + const bool& Mipmap, const Texture::ClampMode& ClampMode, + const bool& CompressTexture, const bool& KeepLocalCopy, + const std::string& FileName ) { TextureLoader myTex( Pixels, Width, Height, Channels, Mipmap, ClampMode, CompressTexture, KeepLocalCopy, FileName ); myTex.load(); return myTex.getTexture(); } -Texture* +TexturePtr TextureFactory::loadFromPack( Pack* Pack, const std::string& FilePackPath, const bool& Mipmap, const Texture::ClampMode& ClampMode, const bool& CompressTexture, const bool& KeepLocalCopy, @@ -90,7 +88,7 @@ TextureFactory::loadFromPack( Pack* Pack, const std::string& FilePackPath, const return myTex.getTexture(); } -Texture* +TexturePtr TextureFactory::loadFromMemory( const unsigned char* ImagePtr, const unsigned int& Size, const bool& Mipmap, const Texture::ClampMode& ClampMode, const bool& CompressTexture, const bool& KeepLocalCopy, @@ -101,7 +99,7 @@ TextureFactory::loadFromMemory( const unsigned char* ImagePtr, const unsigned in return myTex.getTexture(); } -Texture* +TexturePtr TextureFactory::loadFromStream( IOStream& Stream, const bool& Mipmap, const Texture::ClampMode& ClampMode, const bool& CompressTexture, const bool& KeepLocalCopy, @@ -112,7 +110,7 @@ TextureFactory::loadFromStream( IOStream& Stream, const bool& Mipmap, return myTex.getTexture(); } -Texture* +TexturePtr TextureFactory::loadFromFile( const std::string& Filepath, const bool& Mipmap, const Texture::ClampMode& ClampMode, const bool& CompressTexture, const bool& KeepLocalCopy, @@ -123,13 +121,13 @@ TextureFactory::loadFromFile( const std::string& Filepath, const bool& Mipmap, return myTex.getTexture(); } -Texture* TextureFactory::pushTexture( const std::string& Filepath, const Uint32& textureHandle, - const unsigned int& Width, const unsigned int& Height, - const unsigned int& ImgWidth, const unsigned int& ImgHeight, - const bool& Mipmap, const unsigned int& Channels, - const Texture::ClampMode& ClampMode, - const bool& CompressTexture, const bool& LocalCopy, - const Uint32& MemSize ) { +TexturePtr TextureFactory::pushTexture( const std::string& Filepath, const Uint32& textureHandle, + const unsigned int& Width, const unsigned int& Height, + const unsigned int& ImgWidth, const unsigned int& ImgHeight, + const bool& Mipmap, const unsigned int& Channels, + const Texture::ClampMode& ClampMode, + const bool& CompressTexture, const bool& LocalCopy, + const Uint32& MemSize ) { Lock l( *this ); std::string FPath( Filepath ); @@ -144,7 +142,7 @@ Texture* TextureFactory::pushTexture( const std::string& Filepath, const Uint32& Tex->create( textureHandle, Width, Height, ImgWidth, ImgHeight, Mipmap, Channels, FPath, ClampMode, CompressTexture, MemSize ); TextureWeakPtr weakTexture( texture ); - mTextures.emplace( resourceId.value(), std::move( texture ) ); + mTextures.emplace( resourceId.value(), texture ); mLiveTextures.emplace( resourceId.value(), LiveTextureRecord{ resourceId, std::move( weakTexture ) } ); mLiveTextureGeneration.fetch_add( 1, std::memory_order_release ); @@ -154,7 +152,7 @@ Texture* TextureFactory::pushTexture( const std::string& Filepath, const Uint32& Tex->unlock( true, false ); } - return Tex; + return texture; } void TextureFactory::bind( const Texture* texture, Texture::CoordinateType coordinateType, @@ -202,7 +200,7 @@ void TextureFactory::bind( const Texture* texture, Texture::CoordinateType coord void TextureFactory::bind( ResourceId textureId, Texture::CoordinateType coordinateType, const Uint32& textureUnit, const bool& forceRebind ) { - bind( getTexture( textureId ), coordinateType, textureUnit, forceRebind ); + bind( getTexture( textureId ).get(), coordinateType, textureUnit, forceRebind ); } void TextureFactory::unloadTextures() { @@ -220,7 +218,7 @@ void TextureFactory::unloadTextures() { Log::debug( "Textures Unloaded." ); } -bool TextureFactory::remove( ResourceId textureId ) { +bool TextureFactory::releaseRetainedTexture( ResourceId textureId ) { TexturePtr texture; { Lock l( *this ); @@ -237,25 +235,6 @@ bool TextureFactory::remove( ResourceId textureId ) { return true; } -bool TextureFactory::remove( Texture* texture ) { - TexturePtr removed; - { - Lock l( *this ); - auto it = std::find_if( mTextures.begin(), mTextures.end(), [texture]( const auto& pair ) { - return pair.second.get() == texture; - } ); - if ( it == mTextures.end() ) - return false; - - removed = std::move( it->second ); - mTextures.erase( it ); - resetTextureBinding( removed.get() ); - } - - removed.reset(); - return true; -} - void TextureFactory::resetTextureBinding( const Texture* texture ) { if ( !texture ) return; @@ -277,22 +256,6 @@ void TextureFactory::setCurrentTexture( const int& textureHandle, const Uint32& mCurrentTexture[TextureUnit] = textureHandle; } -std::vector TextureFactory::getTextures() { - Lock l( *this ); - - std::vector textures; - textures.reserve( mTextures.size() ); - - for ( const auto& texture : mTextures ) { - Texture* Tex = texture.second.get(); - - if ( Tex ) - textures.push_back( Tex ); - } - - return textures; -} - TextureRegistrySnapshot TextureFactory::snapshotTextures() { struct LockedTextureRecord { LiveTextureRecord record; @@ -423,22 +386,14 @@ bool TextureFactory::existsId( ResourceId textureId ) { return mTextures.find( textureId.value() ) != mTextures.end(); } -bool TextureFactory::exists( const Texture* tex ) { - Lock l( *this ); - - return std::find_if( mTextures.begin(), mTextures.end(), [tex]( const auto& pair ) { - return pair.second.get() == tex; - } ) != mTextures.end(); -} - -Texture* TextureFactory::getTexture( ResourceId textureId ) { +TexturePtr TextureFactory::getTexture( ResourceId textureId ) { Lock l( *this ); auto it = mTextures.find( textureId.value() ); - return it != mTextures.end() ? it->second.get() : NULL; + return it != mTextures.end() ? it->second : TexturePtr{}; } -Texture* TextureFactory::getByName( const std::string& Name ) { +TexturePtr TextureFactory::getByName( const std::string& Name ) { return getByHash( String::hash( Name ) ); } @@ -459,13 +414,13 @@ unsigned int TextureFactory::getTextureMemorySize() { return static_cast( memorySize ); } -Texture* TextureFactory::getByHash( const String::HashType& hash ) { +TexturePtr TextureFactory::getByHash( const String::HashType& hash ) { Lock l( *this ); Uint64 latestId = 0; - Texture* latestTexture = NULL; + TexturePtr latestTexture; for ( const auto& texture : mTextures ) { - Texture* tTex = texture.second.get(); + const TexturePtr& tTex = texture.second; if ( NULL != tTex && texture.first > latestId && tTex->getHashName() == hash ) { latestId = texture.first; diff --git a/src/eepp/graphics/textureloader.cpp b/src/eepp/graphics/textureloader.cpp index c31615108..72ab35565 100644 --- a/src/eepp/graphics/textureloader.cpp +++ b/src/eepp/graphics/textureloader.cpp @@ -432,7 +432,7 @@ const std::string& TextureLoader::getFilepath() const { return mFilepath; } -Texture* TextureLoader::getTexture() const { +const TexturePtr& TextureLoader::getTexture() const { return mTexture; } @@ -445,17 +445,18 @@ void TextureLoader::setFormatConfiguration( mFormatConfiguration = formatConfiguration; } -void TextureLoader::unload() { - if ( mLoaded && mTexture != nullptr ) { - TextureFactory::instance()->remove( mTexture->getTextureId() ); - - reset(); - } -} - void TextureLoader::reset() { - mPixels = nullptr; - mTexture = nullptr; + if ( mTexture ) { + if ( TextureFactory* factory = TextureFactory::existsSingleton() ) + factory->releaseRetainedTexture( mTexture->getTextureId() ); + } + + if ( TEX_LT_PIXELS != mLoadType ) { + eeSAFE_FREE( mPixels ); + } else { + mPixels = nullptr; + } + mTexture.reset(); mImgWidth = 0; mImgHeight = 0; mWidth = 0; diff --git a/src/eepp/graphics/textureregion.cpp b/src/eepp/graphics/textureregion.cpp index 4137fb8b5..aa7305969 100644 --- a/src/eepp/graphics/textureregion.cpp +++ b/src/eepp/graphics/textureregion.cpp @@ -36,40 +36,40 @@ TextureRegion* TextureRegion::New( ResourceId textureId, const Rect& srcRect, co destSize, offset, name ) ); } -TextureRegion* TextureRegion::New( Texture* tex, const std::string& name ) { - return eeNew( TextureRegion, ( tex, name ) ); +TextureRegion* TextureRegion::New( TexturePtr tex, const std::string& name ) { + return eeNew( TextureRegion, ( std::move( tex ), name ) ); } -TextureRegion* TextureRegion::New( Texture* tex, const Rect& srcRect, const std::string& name ) { - return eeNew( TextureRegion, ( tex, srcRect, name ) ); +TextureRegion* TextureRegion::New( TexturePtr tex, const Rect& srcRect, const std::string& name ) { + return eeNew( TextureRegion, ( std::move( tex ), srcRect, name ) ); } -TextureRegion* TextureRegion::New( Texture* tex, const Rect& srcRect, const Sizef& destSize, +TextureRegion* TextureRegion::New( TexturePtr tex, const Rect& srcRect, const Sizef& destSize, const std::string& name ) { - return eeNew( TextureRegion, ( tex, srcRect, destSize, name ) ); + return eeNew( TextureRegion, ( std::move( tex ), srcRect, destSize, name ) ); } -TextureRegion* TextureRegion::New( Texture* tex, const Rect& srcRect, const Sizef& destSize, +TextureRegion* TextureRegion::New( TexturePtr tex, const Rect& srcRect, const Sizef& destSize, const Vector2i& offset, const std::string& name ) { - return eeNew( TextureRegion, ( tex, srcRect, destSize, offset, name ) ); + return eeNew( TextureRegion, ( std::move( tex ), srcRect, destSize, offset, name ) ); } TextureRegion::TextureRegion() : DrawableResource( Drawable::TEXTUREREGION ), mPixels( NULL ), mAlphaMask( NULL ), - mTexture( NULL ), + mTexture(), mSrcRect( Rect( 0, 0, 0, 0 ) ), mOriDestSize( 0, 0 ), mDestSize( 0, 0 ), mOffset( 0, 0 ), mPixelDensity( 1 ) {} -TextureRegion::TextureRegion( Texture* tex, const std::string& name ) : +TextureRegion::TextureRegion( TexturePtr tex, const std::string& name ) : DrawableResource( Drawable::TEXTUREREGION, name ), mPixels( NULL ), mAlphaMask( NULL ), - mTexture( tex ), + mTexture( std::move( tex ) ), mSrcRect( Rect( 0, 0, NULL != mTexture ? mTexture->getImageWidth() : 0, NULL != mTexture ? mTexture->getImageHeight() : 0 ) ), mOriDestSize( PixelDensity::dpToPx( mSrcRect.getSize().asFloat() ) ), @@ -77,11 +77,11 @@ TextureRegion::TextureRegion( Texture* tex, const std::string& name ) : mOffset( 0, 0 ), mPixelDensity( 1 ) {} -TextureRegion::TextureRegion( Texture* tex, const Rect& SrcRect, const std::string& name ) : +TextureRegion::TextureRegion( TexturePtr tex, const Rect& SrcRect, const std::string& name ) : DrawableResource( Drawable::TEXTUREREGION, name ), mPixels( NULL ), mAlphaMask( NULL ), - mTexture( tex ), + mTexture( std::move( tex ) ), mSrcRect( SrcRect ), mOriDestSize( PixelDensity::dpToPx( Sizef( (Float)( mSrcRect.Right - mSrcRect.Left ), (Float)( mSrcRect.Bottom - mSrcRect.Top ) ) ) ), @@ -89,24 +89,24 @@ TextureRegion::TextureRegion( Texture* tex, const Rect& SrcRect, const std::stri mOffset( 0, 0 ), mPixelDensity( 1 ) {} -TextureRegion::TextureRegion( Texture* tex, const Rect& SrcRect, const Sizef& DestSize, +TextureRegion::TextureRegion( TexturePtr tex, const Rect& SrcRect, const Sizef& DestSize, const std::string& name ) : DrawableResource( Drawable::TEXTUREREGION, name ), mPixels( NULL ), mAlphaMask( NULL ), - mTexture( tex ), + mTexture( std::move( tex ) ), mSrcRect( SrcRect ), mOriDestSize( DestSize ), mDestSize( DestSize ), mOffset( 0, 0 ), mPixelDensity( 1 ) {} -TextureRegion::TextureRegion( Texture* tex, const Rect& SrcRect, const Sizef& DestSize, +TextureRegion::TextureRegion( TexturePtr tex, const Rect& SrcRect, const Sizef& DestSize, const Vector2i& Offset, const std::string& name ) : DrawableResource( Drawable::TEXTUREREGION, name ), mPixels( NULL ), mAlphaMask( NULL ), - mTexture( tex ), + mTexture( std::move( tex ) ), mSrcRect( SrcRect ), mOriDestSize( DestSize ), mDestSize( DestSize ), @@ -121,8 +121,8 @@ void TextureRegion::setTextureId( ResourceId textureId ) { mTexture = TextureFactory::instance()->getTexture( textureId ); } -void TextureRegion::setTexture( Texture* texture ) { - mTexture = texture; +void TextureRegion::setTexture( TexturePtr texture ) { + mTexture = std::move( texture ); } const Rect& TextureRegion::getSrcRect() const { @@ -199,7 +199,7 @@ void TextureRegion::draw( const Vector2f& position, const Sizef& size ) { mDestSize = oldSize; } -Graphics::Texture* TextureRegion::getTexture() { +const TexturePtr& TextureRegion::getTexture() const { return mTexture; } diff --git a/src/eepp/ui/tools/uicolorpicker.cpp b/src/eepp/ui/tools/uicolorpicker.cpp index 4d1c0a778..8279e432b 100644 --- a/src/eepp/ui/tools/uicolorpicker.cpp +++ b/src/eepp/ui/tools/uicolorpicker.cpp @@ -287,8 +287,8 @@ UIColorPicker::UIColorPicker( UIWindow* attachTo, const UIColorPicker::ColorPick mRoot->on( Event::OnLayoutUpdate, [this]( const Event* ) { if ( mHuePicker->getDrawable() == nullptr ) { - mHuePicker->setDrawable( createHueTexture( mHuePicker->getPixelsSize() ), true ); - mCurrentColor->setBackgroundDrawable( createGridTexture(), true ); + mHuePicker->setDrawable( createHueTexture( mHuePicker->getPixelsSize() ) ); + mCurrentColor->setBackgroundDrawable( TextureRegion::New( createGridTexture() ), true ); mCurrentColor->setBackgroundRepeat( "repeat" ); updateAll(); } @@ -369,7 +369,7 @@ void UIColorPicker::windowClose( const Event* ) { eeDelete( this ); } -Texture* UIColorPicker::createHueTexture( const Sizef& size ) { +TexturePtr UIColorPicker::createHueTexture( const Sizef& size ) { Image image( 1, (Uint32)size.getHeight(), 3 ); for ( Uint32 y = 0; y < image.getHeight(); y++ ) { @@ -385,7 +385,7 @@ Texture* UIColorPicker::createHueTexture( const Sizef& size ) { image.getChannels() ); } -Texture* UIColorPicker::createGridTexture() { +TexturePtr UIColorPicker::createGridTexture() { Sizef size( PixelDensity::dpToPx( Sizef( 26, 24 ) ) ); Image image( size.getWidth(), size.getHeight(), 3, Color( 128, 128, 128, 255 ) ); Color highlightColor( 204, 204, 204, 255 ); @@ -573,7 +573,7 @@ void UIColorPicker::registerEvents() { ->setPosition( 0, 0 ) ->setSize( mRoot->getSceneNode()->getSize() ); mCoverWidget->setAnchors( UI_ANCHOR_LEFT | UI_ANCHOR_TOP | UI_ANCHOR_RIGHT | - UI_ANCHOR_BOTTOM ); + UI_ANCHOR_BOTTOM ); mCoverWidget->on( Event::MouseMove, [this]( const Event* event ) { Vector2i position = reinterpret_cast( event )->getPosition(); setColor( GLi->readPixel( position.x, diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp index 47afee452..3fabb31ad 100644 --- a/src/eepp/ui/tools/uidiffview.cpp +++ b/src/eepp/ui/tools/uidiffview.cpp @@ -67,7 +67,6 @@ static Sprite* setImageViewerImage( UIImageViewer* viewer, Image* image ) { auto sprite = Sprite::New(); sprite->createStatic( texture ); - sprite->setAsTextureOwner( true ); sprite->setAsTextureRegionOwner( true ); viewer->reset(); diff --git a/src/eepp/ui/tools/uiimageviewer.cpp b/src/eepp/ui/tools/uiimageviewer.cpp index 866c82076..e6d0d7c37 100644 --- a/src/eepp/ui/tools/uiimageviewer.cpp +++ b/src/eepp/ui/tools/uiimageviewer.cpp @@ -167,7 +167,6 @@ void UIImageViewer::loadImageAsync( std::string_view path, bool isContents, bool : TextureFactory::instance()->loadFromFile( path ); Sprite* sprite = Sprite::New(); sprite->createStatic( tex ); - sprite->setAsTextureOwner( true ); sprite->setAsTextureRegionOwner( true ); image = sprite; } else { @@ -175,7 +174,6 @@ void UIImageViewer::loadImageAsync( std::string_view path, bool isContents, bool ? (IOStream*)new IOStreamMemory( path.c_str(), path.size() ) : (IOStream*)new IOStreamFile( path ); Sprite* sprite = Sprite::fromGif( *stream ); - sprite->setAsTextureOwner( true ); sprite->setAsTextureRegionOwner( true ); sprite->setAutoAnimate( false ); image = sprite; diff --git a/src/eepp/ui/uiicon.cpp b/src/eepp/ui/uiicon.cpp index 2e1745674..e3a95533c 100644 --- a/src/eepp/ui/uiicon.cpp +++ b/src/eepp/ui/uiicon.cpp @@ -77,7 +77,7 @@ UISVGIcon::~UISVGIcon() {} Drawable* UISVGIcon::getSize( const int& size ) const { auto it = mSVGs.find( size ); if ( it != mSVGs.end() ) - return it->second; + return it->second.get(); Image::FormatConfiguration format; if ( mOriSize == Sizei::Zero ) { @@ -91,12 +91,12 @@ Drawable* UISVGIcon::getSize( const int& size ) const { } } format.svgScale( size / (Float)eemax( mOriSize.x, mOriSize.y ) ); - Texture* texture = TextureFactory::instance()->loadFromMemory( + TexturePtr texture = TextureFactory::instance()->loadFromMemory( (const unsigned char*)&mSVGXml[0], mSVGXml.size(), false, Texture::ClampMode::ClampToEdge, false, false, format ); mSVGs[size] = texture; - return texture; + return texture.get(); } UISVGIcon::UISVGIcon( const std::string& name, const std::string& svgXML ) : diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index fb25d9510..72017f08c 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -38,11 +38,11 @@ std::string getTextureCacheName( const Network::URI& uri ) { return filePath; } -Texture* loadFileTextureCached( const std::string& filePath, const std::string& cacheName ) { +TexturePtr loadFileTextureCached( const std::string& filePath, const std::string& cacheName ) { static std::mutex loadMutex; std::lock_guard lock( loadMutex ); - if ( Texture* texture = TextureFactory::instance()->getByName( cacheName ) ) + if ( TexturePtr texture = TextureFactory::instance()->getByName( cacheName ) ) return texture; return TextureFactory::instance()->loadFromFile( @@ -136,6 +136,13 @@ UIImage* UIImage::setDrawable( Drawable* drawable, bool ownIt ) { return this; } +UIImage* UIImage::setDrawable( TexturePtr texture ) { + Texture* drawable = texture.get(); + setDrawable( drawable, false ); + mTexture = std::move( texture ); + return this; +} + void UIImage::onAutoSize() { if ( nullptr == mDrawable ) return; @@ -318,6 +325,8 @@ void UIImage::safeDeleteDrawable() { mDrawableOwner = false; } + + mTexture.reset(); } void UIImage::onDrawableResourceEvent( DrawableResource::Event event, DrawableResource* ) { @@ -344,8 +353,8 @@ bool UIImage::loadFileDrawable( const Network::URI& uri ) { Uint64 loadId = ++mRemoteImageLoadId; std::string filePath = uri.getFSPath(); std::string cacheName = getTextureCacheName( uri ); - if ( Texture* texture = TextureFactory::instance()->getByName( cacheName ) ) { - setDrawable( texture, false ); + if ( TexturePtr texture = TextureFactory::instance()->getByName( cacheName ) ) { + setDrawable( std::move( texture ) ); return true; } @@ -361,7 +370,7 @@ bool UIImage::loadFileDrawable( const Network::URI& uri ) { !alive || !alive->load( std::memory_order_acquire ) ) return; - Texture* texture = loadFileTextureCached( filePath, cacheName ); + TexturePtr texture = loadFileTextureCached( filePath, cacheName ); if ( texture == nullptr ) return; @@ -371,7 +380,7 @@ bool UIImage::loadFileDrawable( const Network::URI& uri ) { loadId != mRemoteImageLoadId ) return; - setDrawable( texture, false ); + setDrawable( std::move( texture ) ); } ); } ); @@ -384,10 +393,10 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { return; std::string url = uri.toString(); - if ( Texture* texture = TextureFactory::instance()->getByName( url ) ) { - if ( mDrawable != texture ) { + if ( TexturePtr texture = TextureFactory::instance()->getByName( url ) ) { + if ( mDrawable != texture.get() ) { ++mRemoteImageLoadId; - setDrawable( texture, false ); + setDrawable( std::move( texture ) ); } return; } @@ -397,10 +406,10 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; Uint64 loadId = ++mRemoteImageLoadId; auto alive = mAsyncImageAlive; - Texture* texture = TextureFactory::instance()->createEmptyTexture( + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); if ( texture ) - setDrawable( texture, false ); + setDrawable( texture ); Http::Request::FieldTable headers; if ( !scene->getReferer().empty() ) @@ -419,7 +428,7 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { [alive, loadId, texture, imageData = std::move( imageData ), this]( UISceneNode* ) mutable { if ( !alive || !alive->load( std::memory_order_acquire ) || - loadId != mRemoteImageLoadId || mDrawable != texture ) + loadId != mRemoteImageLoadId || mDrawable != texture.get() ) return; Image image( reinterpret_cast( imageData.data() ), diff --git a/src/eepp/ui/uinodedrawable.cpp b/src/eepp/ui/uinodedrawable.cpp index bad00f63e..c9399343b 100644 --- a/src/eepp/ui/uinodedrawable.cpp +++ b/src/eepp/ui/uinodedrawable.cpp @@ -632,6 +632,7 @@ void UINodeDrawable::LayerDrawable::setDrawable( Drawable* drawable, const bool& eeSAFE_DELETE( mDrawable ); } } + mTexture.reset(); mDrawable = drawable; mDrawableRef = ""; @@ -653,6 +654,12 @@ void UINodeDrawable::LayerDrawable::setDrawable( Drawable* drawable, const bool& } } +void UINodeDrawable::LayerDrawable::setDrawable( TexturePtr texture ) { + Texture* drawable = texture.get(); + setDrawable( drawable, false ); + mTexture = std::move( texture ); +} + void UINodeDrawable::LayerDrawable::setDrawable( const std::string& drawableRef ) { if ( drawableRef == "none" ) { setDrawable( nullptr, false ); @@ -705,10 +712,10 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value return true; std::string url = uri.toString(); - if ( Texture* texture = TextureFactory::instance()->getByName( url ) ) { - if ( mDrawable != texture ) { + if ( TexturePtr texture = TextureFactory::instance()->getByName( url ) ) { + if ( mDrawable != texture.get() ) { ++mRemoteDrawableLoadId; - setDrawable( texture, false ); + setDrawable( std::move( texture ) ); } return true; } @@ -718,10 +725,10 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; Uint64 loadId = ++mRemoteDrawableLoadId; auto alive = mAsyncDrawableAlive; - Texture* texture = TextureFactory::instance()->createEmptyTexture( + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); if ( texture ) - setDrawable( texture, false ); + setDrawable( texture ); Http::Request::FieldTable headers; if ( !scene->getReferer().empty() ) @@ -740,7 +747,7 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value [alive, loadId, texture, imageData = std::move( imageData ), this]( UISceneNode* ) mutable { if ( !alive || !alive->load( std::memory_order_acquire ) || - loadId != mRemoteDrawableLoadId || mDrawable != texture ) + loadId != mRemoteDrawableLoadId || mDrawable != texture.get() ) return; Image image( reinterpret_cast( imageData.data() ), diff --git a/src/eepp/ui/uisvg.cpp b/src/eepp/ui/uisvg.cpp index c0705df91..03edcfafa 100644 --- a/src/eepp/ui/uisvg.cpp +++ b/src/eepp/ui/uisvg.cpp @@ -98,15 +98,14 @@ void UISvg::scheduleRasterize() { } void UISvg::rasterizeSvg( const std::string& svgXml ) { - Texture* texture = TextureFactory::instance()->loadFromMemory( + TexturePtr texture = TextureFactory::instance()->loadFromMemory( (const unsigned char*)svgXml.data(), svgXml.size() ); if ( !texture ) return; Sprite* sprite = Sprite::New(); - sprite->createStatic( texture ); - sprite->setAsTextureOwner( true ); + sprite->createStatic( std::move( texture ) ); sprite->setAsTextureRegionOwner( true ); auto spriteTransfer = std::make_shared( sprite ); diff --git a/src/examples/sprites/sprites.cpp b/src/examples/sprites/sprites.cpp index 5419aeb33..baa9b6a09 100644 --- a/src/examples/sprites/sprites.cpp +++ b/src/examples/sprites/sprites.cpp @@ -101,8 +101,8 @@ EE_MAIN_FUNC int main( int, char*[] ) { FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); // Load the rock texture - Texture* PlanetTex = TextureFactory::instance()->loadFromFile( "assets/sprites/7.png" ); - Texture* RockTex = TextureFactory::instance()->loadFromFile( "assets/sprites/5.png" ); + TexturePtr PlanetTex = TextureFactory::instance()->loadFromFile( "assets/sprites/7.png" ); + TexturePtr RockTex = TextureFactory::instance()->loadFromFile( "assets/sprites/5.png" ); // Load a previously generated texture atlas that contains the TextureRegions needed to load // an animated sprite diff --git a/src/modules/maps/include/eepp/maps/tilemap.hpp b/src/modules/maps/include/eepp/maps/tilemap.hpp index ed75446c4..d07791f68 100644 --- a/src/modules/maps/include/eepp/maps/tilemap.hpp +++ b/src/modules/maps/include/eepp/maps/tilemap.hpp @@ -194,7 +194,7 @@ class EE_MAPS_API TileMap { void setUpdateCallback( MapUpdateCb Cb ); - Texture* getBlankTileTexture(); + const TexturePtr& getBlankTileTexture() const; bool isTileBlocked( const Vector2i& TilePos ); @@ -269,7 +269,7 @@ class EE_MAPS_API TileMap { PropertiesMap mProperties; GOTypesList mObjTypes; CreateGOCb mCreateGOCb; - Texture* mTileTex; + TexturePtr mTileTex; Rectf mScreenAABB; MapLightManager* mLightManager; MapDrawCb mDrawCb; diff --git a/src/modules/maps/src/eepp/maps/mapobjectlayer.cpp b/src/modules/maps/src/eepp/maps/mapobjectlayer.cpp index 8cf969e06..08d2e6496 100644 --- a/src/modules/maps/src/eepp/maps/mapobjectlayer.cpp +++ b/src/modules/maps/src/eepp/maps/mapobjectlayer.cpp @@ -41,7 +41,7 @@ void MapObjectLayer::draw( const Vector2f& Offset ) { ( *it )->draw(); } - Texture* Tex = mMap->getBlankTileTexture(); + const TexturePtr& Tex = mMap->getBlankTileTexture(); if ( mMap->getShowBlocked() && NULL != Tex ) { Color Col( 255, 0, 0, 200 ); diff --git a/src/modules/maps/src/eepp/maps/tilemap.cpp b/src/modules/maps/src/eepp/maps/tilemap.cpp index 1b8b1ed5b..005b0c8b4 100644 --- a/src/modules/maps/src/eepp/maps/tilemap.cpp +++ b/src/modules/maps/src/eepp/maps/tilemap.cpp @@ -128,9 +128,9 @@ void TileMap::createEmptyTile() { std::string tileName( String::format( "maptile-%dx%d-%u", mTileSize.getWidth(), mTileSize.getHeight(), mGridLinesColor.getValue() ) ); - Texture* Tex = TF->getByName( tileName ); + TexturePtr texture = TF->getByName( tileName ); - if ( NULL == Tex ) { + if ( NULL == texture ) { Uint32 x, y; Color Col( mGridLinesColor ); @@ -152,7 +152,7 @@ void TileMap::createEmptyTile() { Img.getChannels(), true, Texture::ClampMode::ClampToEdge, false, false, tileName ); } else { - mTileTex = Tex; + mTileTex = std::move( texture ); } } @@ -1465,7 +1465,7 @@ void TileMap::setUpdateCallback( MapUpdateCb Cb ) { mUpdateCb = Cb; } -Texture* TileMap::getBlankTileTexture() { +const TexturePtr& TileMap::getBlankTileTexture() const { return mTileTex; } diff --git a/src/modules/maps/src/eepp/maps/tilemaplayer.cpp b/src/modules/maps/src/eepp/maps/tilemaplayer.cpp index 0be5447fa..272ebd330 100644 --- a/src/modules/maps/src/eepp/maps/tilemaplayer.cpp +++ b/src/modules/maps/src/eepp/maps/tilemaplayer.cpp @@ -38,7 +38,7 @@ void TileMapLayer::draw( const Vector2f& Offset ) { } } - Texture* Tex = mMap->getBlankTileTexture(); + const TexturePtr& Tex = mMap->getBlankTileTexture(); if ( mMap->getShowBlocked() && NULL != Tex ) { for ( Int32 x = start.x; x < end.x; x++ ) { diff --git a/src/tests/test_all/test.cpp b/src/tests/test_all/test.cpp index 49b26cb99..1ac2f1bbf 100644 --- a/src/tests/test_all/test.cpp +++ b/src/tests/test_all/test.cpp @@ -974,18 +974,21 @@ void EETest::createNewUI() { SceneManager::instance()->getUISceneNode()->bind( "gridlayout", gridLayout ); if ( NULL != gridLayout ) { - std::vector textures = TextureFactory::instance()->getTextures(); + TextureRegistrySnapshot textures = TextureFactory::instance()->snapshotTextures(); if ( textures.size() > 0 ) { for ( std::size_t i = 0; i < textures.size(); i++ ) { + TexturePtr texture = textures[i].texture.lock(); + if ( !texture ) + continue; UIImage* img = UIImage::New(); - img->setDrawable( textures[i] ) + img->setDrawable( texture ) ->setScaleType( UIScaleType::FitInside ) ->setGravity( UI_HALIGN_CENTER | UI_VALIGN_CENTER ) ->setEnabled( false ) ->setParent( gridLayout ); - img->setBackgroundColor( Color::fromPointer( textures[i] ) ); + img->setBackgroundColor( Color::fromPointer( texture.get() ) ); } } } @@ -1368,8 +1371,11 @@ void EETest::loadTextures() { std::string name( files[i] ); if ( "jpg" == FileSystem::fileExtension( name ) ) { - mResLoad.add( [this, name = std::move( name )] { - TextureFactory::instance()->loadFromPack( PakTest, name ); + const std::size_t textureIndex = mLoadedTextures.size(); + mLoadedTextures.emplace_back(); + mResLoad.add( [this, textureIndex, name = std::move( name )] { + mLoadedTextures[textureIndex] = + TextureFactory::instance()->loadFromPack( PakTest, name ); } ); } } @@ -1401,8 +1407,7 @@ void EETest::loadTextures() { Tiles[i] = SG->getByName( String::toString( i + 1 ) ); } - Tiles[6] = - SG->add( TF->loadFromFile( MyPath + "sprites/objects/1.png" )->getTextureId(), "7" ); + Tiles[6] = SG->add( TF->loadFromFile( MyPath + "sprites/objects/1.png" ), "7" ); #ifdef EE_GLES Image tImg( MyPath + "sprites/objects/2.png", 4 ); @@ -1431,7 +1436,7 @@ void EETest::loadTextures() { PS[3].create( ParticleEffect::Fire, 350, TN[5], Vector2f( -50.f, -50.f ), 32, true ); PS[4].create( ParticleEffect::Fire, 350, TN[5], Vector2f( -50.f, -50.f ), 32, true ); - Texture* Tex = TNP[2]; + Texture* Tex = TNP[2].get(); if ( NULL != Tex && Tex->lock() ) { int w = (int)Tex->getWidth(); @@ -1464,7 +1469,7 @@ void EETest::loadTextures() { CurMan->set( Cursor::SysHand ); CurMan->setGlobalCursor( Cursor::Arrow, - CurMan->add( CurMan->create( CursorP[0], Vector2i( 1, 1 ), "cursor_special" ) ) ); + CurMan->add( CurMan->create( CursorP[0].get(), Vector2i( 1, 1 ), "cursor_special" ) ) ); CurMan->set( Cursor::Arrow ); CL1.addFrame( TN[2] ); @@ -1522,7 +1527,13 @@ void EETest::screen1() { void EETest::screen2() { if ( mResLoad.isLoaded() ) { - Texture* TexLoaded = TF->getByName( "1.jpg" ); + TexturePtr TexLoaded; + for ( const TexturePtr& texture : mLoadedTextures ) { + if ( texture && texture->getName() == "1.jpg" ) { + TexLoaded = texture; + break; + } + } if ( NULL != TexLoaded ) TexLoaded->draw( 0, 0 ); diff --git a/src/tests/test_all/test.hpp b/src/tests/test_all/test.hpp index 162626033..86f159690 100644 --- a/src/tests/test_all/test.hpp +++ b/src/tests/test_all/test.hpp @@ -50,7 +50,7 @@ class EETest : private Thread { Uint32 lasttick; std::vector TN; - std::vector TNP; + std::vector TNP; std::vector Tiles; std::vector PS; @@ -97,7 +97,7 @@ class EETest : private Thread { Interpolation2d WP; Int32 PartsNum; ResourceId Cursor[1]; - Texture* CursorP[1]; + TexturePtr CursorP[1]; std::string mInfo; bool MultiViewportMode; @@ -123,6 +123,7 @@ class EETest : private Thread { String mBuddha; ResourceLoader mResLoad; + std::vector mLoadedTextures; void onTextureLoaded( ResourceLoader* ObjLoaded ); void createUI(); diff --git a/src/tests/unit_tests/fontrendering_tests.cpp b/src/tests/unit_tests/fontrendering_tests.cpp index 1e70616d5..eea02bcaf 100644 --- a/src/tests/unit_tests/fontrendering_tests.cpp +++ b/src/tests/unit_tests/fontrendering_tests.cpp @@ -1110,7 +1110,7 @@ UTEST( FontRendering, TextLayoutWrap ) { FontTrueType* font = static_cast( app.getUI()->getUIThemeManager()->getDefaultFont() ); auto fontSize = 16; - Texture* fontTexture = font->getTexture( fontSize ); + const TexturePtr& fontTexture = font->getTexture( fontSize ); BR->setBlendMode( BlendMode::Alpha() ); BR->quadsBegin(); BR->setTexture( fontTexture, fontTexture->getCoordinateType() ); diff --git a/src/tests/unit_tests/resource_prerequisite_tests.cpp b/src/tests/unit_tests/resource_prerequisite_tests.cpp index 69fe3f3cc..d99b31590 100644 --- a/src/tests/unit_tests/resource_prerequisite_tests.cpp +++ b/src/tests/unit_tests/resource_prerequisite_tests.cpp @@ -178,8 +178,8 @@ UTEST( ResourcePrerequisites, textureAtlasLoaderAppliesFilterToEveryTexture ) { ContextSettings( false, 0, 0, GLv_default, true, false ) ); { - Texture* first = TextureFactory::instance()->createEmptyTexture( 1, 1 ); - Texture* second = TextureFactory::instance()->createEmptyTexture( 1, 1 ); + TexturePtr first = TextureFactory::instance()->createEmptyTexture( 1, 1 ); + TexturePtr second = TextureFactory::instance()->createEmptyTexture( 1, 1 ); ASSERT_TRUE( first != NULL ); ASSERT_TRUE( second != NULL ); @@ -207,12 +207,14 @@ UTEST( ResourcePrerequisites, textureAtlasLoaderAcceptsFilterBeforeAtlasExists ) UTEST( ResourcePrerequisites, textureRegistryTracksStableIdentityAndMemoryWithoutOwning ) { createLifecycleTestWindow( "Texture registry identity test" ); TextureFactory* factory = TextureFactory::instance(); - Texture* first = factory->createEmptyTexture( 2, 3, 4, Color::Transparent, false, - Texture::ClampMode::ClampToEdge, false, false, - "registry-first" ); - Texture* second = factory->createEmptyTexture( 1, 1, 4, Color::Transparent, false, - Texture::ClampMode::ClampToEdge, false, false, - "registry-second" ); + Uint8 firstPixels[2 * 3 * 4]{}; + TextureLoader firstLoader( firstPixels, 2, 3, 4, false, Texture::ClampMode::ClampToEdge, false, + false, "registry-first" ); + firstLoader.load(); + TexturePtr first = firstLoader.getTexture(); + TexturePtr second = factory->createEmptyTexture( 1, 1, 4, Color::Transparent, false, + Texture::ClampMode::ClampToEdge, false, false, + "registry-second" ); ASSERT_TRUE( first != nullptr ); ASSERT_TRUE( second != nullptr ); @@ -242,7 +244,8 @@ UTEST( ResourcePrerequisites, textureRegistryTracksStableIdentityAndMemoryWithou TextureWeakPtr firstWeak = firstRecord->texture; TexturePtr retainedFirst = firstWeak.lock(); ASSERT_TRUE( retainedFirst != nullptr ); - ASSERT_TRUE( factory->remove( first ) ); + firstLoader.reset(); + first.reset(); EXPECT_FALSE( firstWeak.expired() ); EXPECT_EQ( factory->getTextureMemorySize(), 4u * 3u * 4u + 4u ); retainedFirst.reset(); @@ -253,19 +256,24 @@ UTEST( ResourcePrerequisites, textureRegistryTracksStableIdentityAndMemoryWithou [firstId]( const auto& record ) { return record.id == firstId; } ) ); EXPECT_EQ( factory->getTextureMemorySize(), 4u ); + second.reset(); Engine::destroySingleton(); createLifecycleTestWindow( "Texture registry identity restart test" ); - Texture* afterRestart = TextureFactory::instance()->createEmptyTexture( 1, 1 ); + TexturePtr afterRestart = TextureFactory::instance()->createEmptyTexture( 1, 1 ); ASSERT_TRUE( afterRestart != nullptr ); EXPECT_TRUE( secondId < afterRestart->getTextureId() ); + afterRestart.reset(); Engine::destroySingleton(); } -UTEST( ResourcePrerequisites, textureFinalReleaseWaitsForDisplayCollection ) { +UTEST( ResourcePrerequisites, pendingBatchRetainsTextureUntilDisplayCollection ) { EE::Window::Window* window = createLifecycleTestWindow( "Texture deferred release test" ); TextureFactory* factory = TextureFactory::instance(); - Texture* texture = factory->createEmptyTexture( 2, 2 ); + Uint8 pixels[2 * 2 * 4]{}; + TextureLoader loader( pixels, 2, 2, 4 ); + loader.load(); + TexturePtr texture = loader.getTexture(); ASSERT_TRUE( texture != nullptr ); bool unloaded = false; @@ -276,7 +284,7 @@ UTEST( ResourcePrerequisites, textureFinalReleaseWaitsForDisplayCollection ) { } ); TextureRegistrySnapshot snapshot = factory->snapshotTextures(); - auto record = std::find_if( snapshot.begin(), snapshot.end(), [texture]( const auto& entry ) { + auto record = std::find_if( snapshot.begin(), snapshot.end(), [&texture]( const auto& entry ) { return entry.id == texture->getTextureId(); } ); ASSERT_TRUE( record != snapshot.end() ); @@ -284,18 +292,46 @@ UTEST( ResourcePrerequisites, textureFinalReleaseWaitsForDisplayCollection ) { TexturePtr retainedTexture = weakTexture.lock(); ASSERT_TRUE( retainedTexture != nullptr ); - ASSERT_TRUE( factory->remove( texture ) ); + loader.reset(); GlobalBatchRenderer::instance()->setTexture( texture ); GlobalBatchRenderer::instance()->batchQuad( 0, 0, 2, 2 ); + texture.reset(); retainedTexture.reset(); - EXPECT_TRUE( weakTexture.expired() ); + EXPECT_FALSE( weakTexture.expired() ); EXPECT_FALSE( unloaded ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); + + window->display( false ); + + EXPECT_TRUE( weakTexture.expired() ); + EXPECT_TRUE( unloaded ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, textureRegionRetainsItsTexture ) { + EE::Window::Window* window = createLifecycleTestWindow( "Texture region ownership test" ); + TextureFactory* factory = TextureFactory::instance(); + Uint8 pixels[2 * 2 * 4]{}; + TextureLoader loader( pixels, 2, 2, 4 ); + loader.load(); + TexturePtr texture = loader.getTexture(); + ASSERT_TRUE( texture != nullptr ); + TextureWeakPtr weakTexture = texture; + + { + TextureRegion region( texture ); + loader.reset(); + texture.reset(); + EXPECT_FALSE( weakTexture.expired() ); + } + + EXPECT_TRUE( weakTexture.expired() ); EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 1 ) ); window->display( false ); - EXPECT_TRUE( unloaded ); EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); Engine::destroySingleton(); } @@ -446,7 +482,7 @@ UTEST( ResourcePrerequisites, engineTeardownReleasesGraphicsBeforeContextsAcross for ( int cycle = 0; cycle < 2; ++cycle ) { createLifecycleTestWindow( "Engine teardown test" ); - Texture* texture = TextureFactory::instance()->createEmptyTexture( 4, 4 ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 4, 4 ); ASSERT_TRUE( texture != nullptr ); NinePatch* ninePatch = NinePatchManager::instance()->add( @@ -468,6 +504,7 @@ UTEST( ResourcePrerequisites, engineTeardownReleasesGraphicsBeforeContextsAcross auto* batch = GlobalBatchRenderer::instance(); batch->setTexture( texture ); batch->batchQuad( 0, 0, 4, 4 ); + texture.reset(); Engine::destroySingleton(); diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 5a098206a..cad48def8 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -3822,7 +3822,7 @@ UTEST( UIBackground, RemoteImageReusesCachedTexture ) { ContextSettings( false, 0, 0, GLv_default, true, false ) ); UISceneNode* sceneNode = init_test_inline_block(); const std::string imageURL = "http://127.0.0.1:1/eepp-cached-background.png"; - Texture* cached = TextureFactory::instance()->createEmptyTexture( + TexturePtr cached = TextureFactory::instance()->createEmptyTexture( 8, 8, 4, Color::White, false, Texture::ClampMode::ClampToEdge, false, false, imageURL ); ASSERT_TRUE( cached != nullptr ); @@ -3842,9 +3842,10 @@ UTEST( UIBackground, RemoteImageReusesCachedTexture ) { ASSERT_TRUE( second->getBackground() != nullptr ); ASSERT_TRUE( first->getBackground()->getLayer( 0 ) != nullptr ); ASSERT_TRUE( second->getBackground()->getLayer( 0 ) != nullptr ); - EXPECT_EQ( cached, first->getBackground()->getLayer( 0 )->getDrawable() ); - EXPECT_EQ( cached, second->getBackground()->getLayer( 0 )->getDrawable() ); + EXPECT_EQ( cached.get(), first->getBackground()->getLayer( 0 )->getDrawable() ); + EXPECT_EQ( cached.get(), second->getBackground()->getLayer( 0 )->getDrawable() ); + cached.reset(); Engine::destroySingleton(); } @@ -5012,7 +5013,7 @@ UTEST( UIHTML, TextureReplaceInvalidatesRichTextAncestors ) { auto* img = images[0]->asType(); ASSERT_TRUE( img != nullptr ); - Texture* texture = + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent ); ASSERT_TRUE( texture != nullptr ); img->setDrawable( texture ); @@ -5031,6 +5032,7 @@ UTEST( UIHTML, TextureReplaceInvalidatesRichTextAncestors ) { EXPECT_GT( body->getPixelsSize().getHeight(), bodyInitialHeight + 150.f ); EXPECT_GT( doc->getPixelsSize().getHeight(), docInitialHeight + 150.f ); + texture.reset(); Engine::destroySingleton(); } @@ -5155,7 +5157,7 @@ UTEST( UIHTML, RemoteImageReusesCachedTexture ) { const std::string imageURL = "http://127.0.0.1:1/eepp-cached-image.png"; UISceneNode* sceneNode = init_test_inline_block(); - Texture* cached = TextureFactory::instance()->createEmptyTexture( + TexturePtr cached = TextureFactory::instance()->createEmptyTexture( 8, 8, 4, Color::White, false, Texture::ClampMode::ClampToEdge, false, false, imageURL ); ASSERT_TRUE( cached != nullptr ); @@ -5182,9 +5184,10 @@ UTEST( UIHTML, RemoteImageReusesCachedTexture ) { auto* second = secondNode->asType(); ASSERT_TRUE( first != nullptr ); ASSERT_TRUE( second != nullptr ); - EXPECT_EQ( cached, first->getDrawable() ); - EXPECT_EQ( cached, second->getDrawable() ); + EXPECT_EQ( cached.get(), first->getDrawable() ); + EXPECT_EQ( cached.get(), second->getDrawable() ); + cached.reset(); Engine::destroySingleton(); } diff --git a/src/tools/ecode/settingsactions.cpp b/src/tools/ecode/settingsactions.cpp index 0a677a4db..5cb80197d 100644 --- a/src/tools/ecode/settingsactions.cpp +++ b/src/tools/ecode/settingsactions.cpp @@ -117,13 +117,13 @@ void SettingsActions::aboutEcode() { UIImage* image = UIImage::New(); image->setParent( msgBox->getContainer()->getFirstChild() ); auto tf = TextureFactory::instance(); - Texture* tex = tf->getByName( "ecode-logo" ); + TexturePtr tex = tf->getByName( "ecode-logo" ); if ( tex == nullptr ) { tex = tf->loadFromFile( mApp->resPath() + "icon/ecode.png" ); if ( tex ) tex->setName( "ecode-logo" ); } - image->setDrawable( tex ); + image->setDrawable( std::move( tex ) ); image->setLayoutGravity( UI_NODE_ALIGN_CENTER ); image->setGravity( UI_NODE_ALIGN_CENTER ); image->setScaleType( UIScaleType::FitInside ); diff --git a/src/tools/uieditor/uieditor.cpp b/src/tools/uieditor/uieditor.cpp index e2b039c02..9194c7f0e 100644 --- a/src/tools/uieditor/uieditor.cpp +++ b/src/tools/uieditor/uieditor.cpp @@ -136,7 +136,6 @@ void App::saveConfig() { void App::unloadImages() { for ( auto it = mImagesLoaded.begin(); it != mImagesLoaded.end(); ++it ) { GlobalTextureAtlas::instance()->remove( it->second ); - TextureFactory::instance()->remove( it->first ); } mImagesLoaded.clear(); } @@ -149,10 +148,11 @@ void App::unloadFonts() { void App::loadImage( std::string path ) { std::string filename( FileSystem::fileRemoveExtension( FileSystem::fileNameFromPath( path ) ) ); - Texture* tex = TextureFactory::instance()->loadFromFile( path ); + TexturePtr tex = TextureFactory::instance()->loadFromFile( path ); if ( tex ) { ResourceId texId = tex->getTextureId(); - TextureRegion* texRegion = GlobalTextureAtlas::instance()->add( texId, filename ); + TextureRegion* texRegion = + GlobalTextureAtlas::instance()->add( std::move( tex ), filename ); mImagesLoaded[texId] = texRegion; } } From 04f600bef59a2386cd7a28926c8a79d1c73dcdec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 19 Jul 2026 23:06:23 -0300 Subject: [PATCH 02/18] graphics: move texture ownership to catalogs and scopes Add ResourceCatalog and ResourceScope to provide explicit strong ownership and deterministic semantic lookup independently from the live texture registry. Let Engine own the global catalog and default Graphics scope, and give each UISceneNode an isolated scope that can be explicitly shared with other scenes. Remove TextureFactory's strong texture retention and name/hash lookup. The factory now observes textures weakly for identity, diagnostics, and deferred destruction without controlling their semantic lifetime. Migrate drawable resolution, texture atlas loading, UI image caches, tile maps, ecode resources, and related tests to scoped lookup and publication. Allow final TexturePtr release from worker threads while keeping actual texture destruction deferred to the graphics thread. Fix mismatched pixel deallocation uncovered by asynchronous texture loading tests. Add ownership, scope isolation, scene sharing, worker release, and decoder allocation regression tests, and update the shared-resource architecture plan through Stage 3. --- .../resource_shared_ownership_architecture.md | 45 +++--- include/eepp/graphics.hpp | 2 + include/eepp/graphics/drawablesearcher.hpp | 5 +- include/eepp/graphics/resource.hpp | 18 +++ include/eepp/graphics/resourcecatalog.hpp | 37 +++++ include/eepp/graphics/resourcescope.hpp | 45 ++++++ include/eepp/graphics/textureatlasloader.hpp | 7 + include/eepp/graphics/texturefactory.hpp | 30 +--- include/eepp/graphics/textureloader.hpp | 2 + include/eepp/ui/uiscenenode.hpp | 12 +- include/eepp/window/engine.hpp | 15 ++ src/eepp/graphics/drawablesearcher.cpp | 41 ++--- src/eepp/graphics/image.cpp | 2 +- src/eepp/graphics/resourcecatalog.cpp | 87 ++++++++++ src/eepp/graphics/resourcescope.cpp | 100 ++++++++++++ src/eepp/graphics/textureatlasloader.cpp | 28 +++- src/eepp/graphics/texturefactory.cpp | 86 ++-------- src/eepp/graphics/textureloader.cpp | 36 +++-- src/eepp/ui/css/drawableimageparser.cpp | 14 +- src/eepp/ui/uiimage.cpp | 27 ++-- src/eepp/ui/uinodedrawable.cpp | 7 +- src/eepp/ui/uiscenenode.cpp | 11 ++ src/eepp/ui/uisprite.cpp | 10 +- src/eepp/window/engine.cpp | 21 ++- src/modules/maps/src/eepp/maps/tilemap.cpp | 6 +- .../resource_prerequisite_tests.cpp | 152 ++++++++++++++++++ src/tests/unit_tests/uihtml_tests.cpp | 5 +- src/tools/ecode/settingsactions.cpp | 7 +- 28 files changed, 687 insertions(+), 171 deletions(-) create mode 100644 include/eepp/graphics/resourcecatalog.hpp create mode 100644 include/eepp/graphics/resourcescope.hpp create mode 100644 src/eepp/graphics/resourcecatalog.cpp create mode 100644 src/eepp/graphics/resourcescope.cpp diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index 52190240d..45bdc5e74 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -1,7 +1,7 @@ # eepp shared-resource ownership architecture -Status: active implementation baseline; Stage 0, prerequisite fixes, Stage 1, and Stage 2 complete; -Stage 3 is next, 2026-07-19. +Status: active implementation baseline; Stage 0 through Stage 3 complete; Stage 4 is next, +2026-07-19. This document freezes the contracts that must be true before the public texture API is changed. The implementation may refine names and small mechanics, but changing an invariant below requires an @@ -19,9 +19,9 @@ The final model is: reporting. It is never searched for semantic names. - Catalogs define names and persistence. - Scopes define which catalogs and typed caches are visible. -- GPU resources remain graphics-thread-affine. The project contract requires final owning releases - and destruction to run through the graphics/update lifecycle rather than supporting arbitrary - last-release threads. +- GPU resources remain graphics-thread-affine. A final owning release may happen on a worker, but + the texture deleter only performs a thread-safe handoff to TextureFactory. Actual destruction runs + through the graphics/display lifecycle. - UI drawable resolution is layered over Graphics resource lookup; browser caching and navigation remain outside Graphics. - A UISceneNode can own a scope and resolver, but neither texture lifetime nor pure Graphics usage @@ -105,10 +105,10 @@ Factories use an equivalent private helper for protected constructors. `std::mak used for tracked eepp resources unless the memory manager is redesigned to understand its combined allocation. No second control block may be created from `handle.get()`. -Texture is the deliberate exception to immediate `eeDelete`: its factory-controlled deleter queues -the final raw object for graphics-thread destruction. `TextureFactory::collectReleasedTextures()` -performs the eventual `eeDelete` after queued rendering has been flushed. This is the same deferred -destruction contract used by scene nodes; it is not a general arbitrary-thread GPU disposal system. +Texture is the deliberate exception to immediate `eeDelete`: its factory-controlled deleter may +queue the final raw object from any thread. `TextureFactory::collectReleasedTextures()` performs the +eventual `eeDelete` on the graphics thread after queued rendering has been flushed. This is the same +deferred destruction contract used by scene nodes; it is not a general GPU disposal system. ### 3.3 Identity, keys, and labels @@ -270,14 +270,14 @@ performs the same collection explicitly because no later display is guaranteed. ### 5.1 Graphics-thread lifetime contract -GPU resources are graphics-thread-affine. Creating, mutating and finally releasing owning handles -must follow the engine's graphics/update lifecycle. `std::shared_ptr` provides ownership safety; it -does not expand eepp's supported threading contract. Async CPU decoding may run elsewhere, but -ownership handoff and final release are marshalled to the main/scene update path unless an existing -API explicitly acquires a shared GL context. +GPU operations remain graphics-context-affine and follow the existing graphics/update or explicitly +shared-context rules. Releasing the final `TexturePtr` is different: it performs no GPU operation and +may enqueue the raw texture from any thread. Only collection and actual destruction require the +graphics thread and a current context. -Debug builds should assert this contract at factory release and collection boundaries. The design -does not add a generic device state, epoch or arbitrary-thread disposal queue for unsupported usage. +Debug builds assert the collection boundary. The design does not add a generic device state, epoch, +or disposal mechanism for other GPU resource families; TextureFactory's small deferred-release queue +is the texture-specific lifetime boundary already required by batched rendering. ### 5.2 Texture deferred destruction @@ -571,7 +571,7 @@ Exit tests: - Pending batches flush before texture collection. - Engine teardown leaves no pending released textures or unexpected live registry entries. - Repeated test-only Engine create/destroy cycles start with empty resource state. -- Wrong-thread final release triggers the documented debug contract assertion. +- Worker-thread final release only queues the texture; it does not run GL or destruction work. - `EE_MEMORY_MANAGER` accurately removes texture allocations through the factory-controlled deleter. ### Stage 2: one complete TexturePtr ownership cut @@ -619,6 +619,15 @@ Exit criteria: ### Stage 3: catalog and scope ownership cutover +Status: complete, 2026-07-19. Engine now owns the global catalog and default Graphics scope; +UISceneNode owns an isolated scope that can be shared explicitly. TextureFactory is an unpinned +creator and weak live registry with no semantic name/hash lookup. Atlas, map, UI image/background, +DrawableSearcher, ecode, tests, and other name-based consumers publish to and resolve through their +explicit scope. Catalog aliases and imports provide intentional persistence and deterministic +sharing. Worker-thread final TexturePtr release is handed to the factory's thread-safe queue and +actual deletion remains display/shutdown-bound. The full cut also corrected TextureLoader's decoder +pixel allocator provenance, which asynchronous scoped loading exposed. + Implement the global catalog, default Graphics scope, application/scene catalogs, explicit imports, immutable keys, and aliases. Move intended persistent resources from temporary factory retention into catalogs/caches. Remove factory-wide strong retention and activate final unpinned creation. @@ -703,7 +712,7 @@ Remove raw-owning `ResourceManager` only when no subclass or consumer depends ### GPU/thread lifetime - Final texture release on the graphics thread queues rather than immediately deleting. -- Wrong-thread final release is detected as a project-contract violation in debug builds. +- Worker-thread final release safely queues; display performs the destruction with a current context. - Display flushes batches before collecting released textures under the current context. - Engine shutdown performs a final collection before TextureFactory/Renderer/context destruction. - No deletion/callback occurs while registry/cache locks are held. diff --git a/include/eepp/graphics.hpp b/include/eepp/graphics.hpp index 4cd5df98a..52cc62dc9 100644 --- a/include/eepp/graphics.hpp +++ b/include/eepp/graphics.hpp @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/include/eepp/graphics/drawablesearcher.hpp b/include/eepp/graphics/drawablesearcher.hpp index 81efb4da8..b42d1e20c 100644 --- a/include/eepp/graphics/drawablesearcher.hpp +++ b/include/eepp/graphics/drawablesearcher.hpp @@ -7,10 +7,13 @@ namespace EE { namespace Graphics { +class ResourceScope; + class EE_API DrawableSearcher { public: static Drawable* searchByName( const std::string& name, bool firstSearchSprite = false, - Network::URI referer = "" ); + Network::URI referer = "", + ResourceScope* resourceScope = nullptr ); static Drawable* searchById( const Uint32& id ); diff --git a/include/eepp/graphics/resource.hpp b/include/eepp/graphics/resource.hpp index 8158269b3..afd5979a3 100644 --- a/include/eepp/graphics/resource.hpp +++ b/include/eepp/graphics/resource.hpp @@ -2,6 +2,8 @@ #define EE_GRAPHICS_RESOURCE_HPP #include +#include +#include #include @@ -25,6 +27,22 @@ class ResourceId { Uint64 mValue{ 0 }; }; +/** Immutable semantic lookup key. Catalog equality always compares the complete key value. */ +class ResourceKey { + public: + ResourceKey() = default; + explicit ResourceKey( std::string value ) : mValue( std::move( value ) ) {} + + const std::string& value() const { return mValue; } + bool empty() const { return mValue.empty(); } + + bool operator==( const ResourceKey& other ) const { return mValue == other.mValue; } + bool operator!=( const ResourceKey& other ) const { return !( *this == other ); } + + private: + std::string mValue; +}; + template using ResourcePtr = std::shared_ptr; template using ResourceWeakPtr = std::weak_ptr; diff --git a/include/eepp/graphics/resourcecatalog.hpp b/include/eepp/graphics/resourcecatalog.hpp new file mode 100644 index 000000000..82bf92ed9 --- /dev/null +++ b/include/eepp/graphics/resourcecatalog.hpp @@ -0,0 +1,37 @@ +#ifndef EE_GRAPHICS_RESOURCECATALOG_HPP +#define EE_GRAPHICS_RESOURCECATALOG_HPP + +#include +#include +#include +#include + +namespace EE { namespace Graphics { + +class ResourceCatalog; +using ResourceCatalogPtr = ResourcePtr; + +/** Strong, named ownership for Graphics resources. The live registry is never searched here. */ +class EE_API ResourceCatalog { + public: + static ResourceCatalogPtr New(); + + void publish( ResourceKey key, TexturePtr texture ); + void publish( std::string key, TexturePtr texture ); + + TexturePtr findTexture( const ResourceKey& key ) const; + TexturePtr findTexture( const std::string& key ) const; + + bool erase( const ResourceKey& key ); + bool erase( const std::string& key ); + void clear(); + std::size_t size() const; + + private: + mutable System::Mutex mMutex; + UnorderedMap mTextures; +}; + +}} // namespace EE::Graphics + +#endif diff --git a/include/eepp/graphics/resourcescope.hpp b/include/eepp/graphics/resourcescope.hpp new file mode 100644 index 000000000..5d6719ae6 --- /dev/null +++ b/include/eepp/graphics/resourcescope.hpp @@ -0,0 +1,45 @@ +#ifndef EE_GRAPHICS_RESOURCESCOPE_HPP +#define EE_GRAPHICS_RESOURCESCOPE_HPP + +#include + +namespace EE { namespace Graphics { + +class ResourceScope; +using ResourceScopePtr = ResourcePtr; + +/** Graphics-only semantic lookup boundary with a local catalog and explicit catalog imports. */ +class EE_API ResourceScope { + public: + static ResourceScopePtr New(); + + ResourceScope(); + + TexturePtr findTexture( const ResourceKey& key ) const; + TexturePtr findTexture( const std::string& key ) const; + + void publishLocal( ResourceKey key, TexturePtr texture ); + void publishLocal( std::string key, TexturePtr texture ); + bool eraseLocal( const ResourceKey& key ); + bool eraseLocal( const std::string& key ); + void clearLocal(); + + void importCatalog( ResourceCatalogPtr catalog ); + bool removeCatalog( const ResourceCatalogPtr& catalog ); + void clearImports(); + + ResourceCatalogPtr getLocalCatalog() const; + + private: + ResourceCatalogPtr mLocalCatalog; + std::vector mImports; + mutable System::Mutex mMutex; +}; + +/** Engine-owned process defaults for pure Graphics and legacy application-wide resolution. */ +EE_API ResourceCatalog& globalResourceCatalog(); +EE_API ResourceScope& defaultResourceScope(); + +}} // namespace EE::Graphics + +#endif diff --git a/include/eepp/graphics/textureatlasloader.hpp b/include/eepp/graphics/textureatlasloader.hpp index 451418fca..de5bd70b7 100644 --- a/include/eepp/graphics/textureatlasloader.hpp +++ b/include/eepp/graphics/textureatlasloader.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -161,6 +162,11 @@ class EE_API TextureAtlasLoader { void setTextureFilter( const Texture::Filter& textureFilter ); + /** Sets the semantic texture lookup boundary used by subsequent loads. */ + void setResourceScope( ResourceScopePtr resourceScope ); + + const ResourceScopePtr& getResourceScope() const; + protected: std::string mTextureAtlasPath; bool mThreaded; @@ -170,6 +176,7 @@ class EE_API TextureAtlasLoader { std::atomic mIsLoading; TextureAtlas* mTextureAtlas; GLLoadCallback mLoadCallback; + ResourceScopePtr mResourceScope; std::vector mTexturesLoaded; struct sTempTexAtlas { diff --git a/include/eepp/graphics/texturefactory.hpp b/include/eepp/graphics/texturefactory.hpp index 71a9874c3..ed8fe5389 100644 --- a/include/eepp/graphics/texturefactory.hpp +++ b/include/eepp/graphics/texturefactory.hpp @@ -21,7 +21,7 @@ struct TextureRegistryRecord { using TextureRegistrySnapshot = std::vector; -/** @brief The Texture Manager Class. Here we do all the textures stuff. (Singleton Class) */ +/** Creates textures and weakly observes every live texture for diagnostics. */ class EE_API TextureFactory : protected Mutex { SINGLETON_DECLARE_HEADERS( TextureFactory ) @@ -172,7 +172,7 @@ class EE_API TextureFactory : protected Mutex { */ void setCurrentTexture( const int& textureHandle, const Uint32& TextureUnit ); - /** Returns the number of textures loaded */ + /** Returns the number of currently live textures. */ Uint32 getTextureCount(); /** @return A non-owning diagnostic snapshot of every currently live texture. */ @@ -201,10 +201,10 @@ class EE_API TextureFactory : protected Mutex { */ unsigned int getValidTextureSize( const unsigned int& Size ); - /** Determines whether the texture identity exists in the factory. */ + /** Determines whether the texture identity is currently live. */ bool existsId( ResourceId textureId ); - /** @return The texture matching @p textureId, or null if it is not factory-retained. */ + /** @return The live texture matching @p textureId, or null if it has expired. */ TexturePtr getTexture( ResourceId textureId ); /** @return The memory used by the textures (in bytes) */ @@ -233,32 +233,17 @@ class EE_API TextureFactory : protected Mutex { const Texture::ClampMode& ClampMode, const bool& CompressTexture, const bool& LocalCopy = false, const Uint32& MemSize = 0 ); - /** Return a texture by it file path name - * @param Name File path name - * @return The texture, NULL if not exists. - */ - TexturePtr getByName( const std::string& Name ); - - /** Return a texture by it hash path name - * @param Hash The file path hash - * @return The texture, NULL if not exists - */ - TexturePtr getByHash( const String::HashType& hash ); - ~TextureFactory(); const Texture::CoordinateType& getLastCoordinateType() const; protected: friend class Texture; - friend class TextureLoader; TextureFactory(); std::vector mCurrentTexture; - using TextureMap = UnorderedMap; - struct LiveTextureRecord { ResourceId id; TextureWeakPtr texture; @@ -268,19 +253,16 @@ class EE_API TextureFactory : protected Mutex { void operator()( Texture* texture ) const noexcept; }; - TextureMap mTextures; UnorderedMap mLiveTextures; std::vector mReleasedTextures; std::atomic mLiveTextureGeneration{ 0 }; Texture::CoordinateType mLastCoordinateType; - void unloadTextures(); - void resetTextureBinding( const Texture* texture ); - bool releaseRetainedTexture( ResourceId textureId ); - + /** Thread-safe final-handle handoff. Actual Texture destruction remains graphics-thread-only. + */ void queueReleasedTexture( Texture* texture ); void diagnoseLiveTexturesAtShutdown(); diff --git a/include/eepp/graphics/textureloader.hpp b/include/eepp/graphics/textureloader.hpp index 00d59e2ef..1307240df 100644 --- a/include/eepp/graphics/textureloader.hpp +++ b/include/eepp/graphics/textureloader.hpp @@ -137,12 +137,14 @@ class EE_API TextureLoader { bool mLoaded{ false }; bool mTexLoaded{ false }; bool mDirectUpload{ false }; + bool mPixelsUseSystemFree{ false }; Image::Format mImgType{ 0 }; int mIsCompressed{ 0 }; Clock mTE; void loadFile(); + void freePixels(); void loadFromFile(); void loadFromMemory(); void loadFromPack(); diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index 121674a63..26d73a761 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -1,6 +1,7 @@ #ifndef EE_UISCENENODE_HPP #define EE_UISCENENODE_HPP +#include #include #include #include @@ -153,8 +154,8 @@ class EE_API UISceneNode : public SceneNode { * * Copies only shared platform/configuration services: dispatcher, DPI/window pointer, * thread pool, color/contrast preferences, and default font/theme pointers. Stylesheets, - * URI, referer, cookies, navigation callbacks, actions, roots, and dirty queues remain owned - * by this scene. + * URI, referer, cookies, navigation callbacks, actions, roots, resource scope, and dirty queues + * remain owned by this scene. */ void initializeEmbeddedFromHost( UISceneNode* hostScene ); @@ -770,6 +771,12 @@ class EE_API UISceneNode : public SceneNode { */ void setThreadPool( const std::shared_ptr& threadPool ); + /** @return The Graphics resource lookup and ownership boundary of this scene. */ + const Graphics::ResourceScopePtr& getResourceScope() const; + + /** Replaces this scene's resource boundary, allowing intentional sharing between scenes. */ + UISceneNode* setResourceScope( Graphics::ResourceScopePtr resourceScope ); + /** * @brief Sets the theme for the entire UI scene. * @@ -902,6 +909,7 @@ class EE_API UISceneNode : public SceneNode { UnorderedMap mFontFaceAliases; UnorderedMap mFontFaceFamilies; std::shared_ptr mAsyncResourceLoadState; + Graphics::ResourceScopePtr mResourceScope; KeyBindings mKeyBindings; std::map mKeyBindingCommands; UnorderedSet mDirtyStyle; diff --git a/include/eepp/window/engine.hpp b/include/eepp/window/engine.hpp index 233c64ec8..d03d97852 100644 --- a/include/eepp/window/engine.hpp +++ b/include/eepp/window/engine.hpp @@ -6,6 +6,13 @@ #include #include +#include + +namespace EE { namespace Graphics { +class ResourceCatalog; +class ResourceScope; +}} // namespace EE::Graphics + namespace EE { namespace System { class IniFile; class Pack; @@ -140,6 +147,12 @@ class EE_API Engine { /** @return The display manager. Holds the physical displays information. */ DisplayManager* getDisplayManager(); + /** @return The catalog used for resources intentionally exported application-wide. */ + std::shared_ptr getGlobalResourceCatalog() const; + + /** @return The default Graphics scope. It explicitly imports the global resource catalog. */ + std::shared_ptr getDefaultResourceScope() const; + /** Open a URL in a separate, system-provided application. * @return true if success */ @@ -156,6 +169,8 @@ class EE_API Engine { PlatformHelper* mPlatformHelper; Pack* mZip; DisplayManager* mDisplayManager; + std::shared_ptr mGlobalResourceCatalog; + std::shared_ptr mDefaultResourceScope; Engine(); diff --git a/src/eepp/graphics/drawablesearcher.cpp b/src/eepp/graphics/drawablesearcher.cpp index 3e84c3ef9..c707bd42f 100644 --- a/src/eepp/graphics/drawablesearcher.cpp +++ b/src/eepp/graphics/drawablesearcher.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -33,7 +34,7 @@ static Drawable* getSprite( const std::string& sprite ) { return NULL; } -static Drawable* searchByNameInternal( const std::string& name ) { +static Drawable* searchByNameInternal( const std::string& name, ResourceScope& resourceScope ) { String::HashType id = String::hash( name ); Drawable* drawable = TextureAtlasManager::instance()->getTextureRegionById( id ); @@ -42,15 +43,15 @@ static Drawable* searchByNameInternal( const std::string& name ) { } if ( NULL == drawable ) { - drawable = TextureFactory::instance()->getByHash( id ).get(); + drawable = resourceScope.findTexture( name ).get(); } return drawable; } -static Drawable* parseDataURI( const std::string& name ) { +static Drawable* parseDataURI( const std::string& name, ResourceScope& scope ) { auto hash = MD5::fromString( name ).toHexString(); - TexturePtr texture = TextureFactory::instance()->getByName( hash ); + TexturePtr texture = scope.findTexture( hash ); Drawable* drawable = texture.get(); std::string::size_type formatAndEncSep; if ( nullptr == drawable && @@ -101,6 +102,7 @@ static Drawable* parseDataURI( const std::string& name ) { if ( tex ) { tex->setName( hash ); + scope.publishLocal( hash, tex ); drawable = tex.get(); } } @@ -108,10 +110,13 @@ static Drawable* parseDataURI( const std::string& name ) { } Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSearchSprite, - Network::URI referer ) { + Network::URI referer, + ResourceScope* requestedResourceScope ) { Drawable* drawable = NULL; if ( name.size() ) { + ResourceScope& resourceScope = + requestedResourceScope ? *requestedResourceScope : defaultResourceScope(); bool searchedSprite = false; if ( firstSearchSprite ) { @@ -133,17 +138,17 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea drawable = TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ); } else if ( String::startsWith( name, "@image/" ) ) { - drawable = TextureFactory::instance()->getByName( name.substr( 7 ) ).get(); + drawable = resourceScope.findTexture( name.substr( 7 ) ).get(); } else if ( String::startsWith( name, "@texture/" ) ) { - drawable = TextureFactory::instance()->getByName( name.substr( 9 ) ).get(); + drawable = resourceScope.findTexture( name.substr( 9 ) ).get(); } else if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) { drawable = getSprite( name.substr( 8 ) ); } else if ( String::startsWith( name, "@drawable/" ) ) { - drawable = searchByNameInternal( name.substr( 10 ) ); + drawable = searchByNameInternal( name.substr( 10 ), resourceScope ); } else if ( String::startsWith( name, "@9p/" ) ) { drawable = NinePatchManager::instance()->getByName( name.substr( 4 ) ); } else { - drawable = searchByNameInternal( name ); + drawable = searchByNameInternal( name, resourceScope ); } } else if ( String::startsWith( name, "file://" ) ) { std::string filePath( name.substr( 7 ) ); @@ -157,22 +162,26 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea FileSystem::filePathRemoveProcessPath( filePath ); - drawable = TextureFactory::instance()->getByName( filePath ).get(); + drawable = resourceScope.findTexture( filePath ).get(); if ( NULL == drawable ) { TexturePtr tex = TextureFactory::instance()->loadFromFile( filePath ); - if ( tex ) + if ( tex ) { + resourceScope.publishLocal( filePath, tex ); drawable = tex.get(); + } } } else if ( String::startsWith( name, "http://" ) || String::startsWith( name, "https://" ) ) { - TexturePtr texture = TextureFactory::instance()->getByName( name ); + TexturePtr texture = resourceScope.findTexture( name ); if ( NULL == texture && Engine::instance()->isSharedGLContextEnabled() ) { texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, name ); + if ( texture ) + resourceScope.publishLocal( name, texture ); std::map headers; if ( !referer.empty() ) @@ -197,9 +206,9 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea drawable = texture.get(); } else if ( String::startsWith( name, "data:image/" ) ) { - drawable = parseDataURI( name ); + drawable = parseDataURI( name, resourceScope ); } else { - drawable = searchByNameInternal( name ); + drawable = searchByNameInternal( name, resourceScope ); } } @@ -212,10 +221,6 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea Drawable* DrawableSearcher::searchById( const Uint32& id ) { Drawable* drawable = TextureAtlasManager::instance()->getTextureRegionById( id ); - if ( NULL == drawable ) { - drawable = TextureFactory::instance()->getByHash( id ).get(); - } - if ( NULL == drawable && sPrintWarnings ) Log::warning( "DrawableSearcher::searchById: \"%ld\" not found", id ); diff --git a/src/eepp/graphics/image.cpp b/src/eepp/graphics/image.cpp index 51e902b06..400d9b83e 100644 --- a/src/eepp/graphics/image.cpp +++ b/src/eepp/graphics/image.cpp @@ -929,7 +929,7 @@ void Image::webpLoad( const Uint8* imageData, size_t imageDataSize ) { } if ( errdec || nullptr == dstImage ) { - eeSAFE_FREE( dstImage ); + free( dstImage ); return; } diff --git a/src/eepp/graphics/resourcecatalog.cpp b/src/eepp/graphics/resourcecatalog.cpp new file mode 100644 index 000000000..729f76ad5 --- /dev/null +++ b/src/eepp/graphics/resourcecatalog.cpp @@ -0,0 +1,87 @@ +#include +#include + +using namespace EE::System; + +namespace EE { namespace Graphics { + +ResourceCatalogPtr ResourceCatalog::New() { + return ResourceCatalogPtr( eeNew( ResourceCatalog, () ), ResourceDeleter() ); +} + +void ResourceCatalog::publish( ResourceKey key, TexturePtr texture ) { + publish( key.value(), std::move( texture ) ); +} + +void ResourceCatalog::publish( std::string key, TexturePtr texture ) { + if ( key.empty() ) + return; + + if ( !texture ) { + erase( key ); + return; + } + + TexturePtr previous; + { + Lock lock( mMutex ); + auto it = mTextures.find( key ); + if ( it == mTextures.end() ) { + mTextures.emplace( std::move( key ), std::move( texture ) ); + return; + } + + previous = std::move( it->second ); + it->second = std::move( texture ); + } + + // A replaced handle may be the final owner. Release it without holding the catalog mutex. + previous.reset(); +} + +TexturePtr ResourceCatalog::findTexture( const ResourceKey& key ) const { + return findTexture( key.value() ); +} + +TexturePtr ResourceCatalog::findTexture( const std::string& key ) const { + Lock lock( mMutex ); + auto it = mTextures.find( key ); + return it != mTextures.end() ? it->second : TexturePtr{}; +} + +bool ResourceCatalog::erase( const ResourceKey& key ) { + return erase( key.value() ); +} + +bool ResourceCatalog::erase( const std::string& key ) { + TexturePtr texture; + { + Lock lock( mMutex ); + auto it = mTextures.find( key ); + if ( it == mTextures.end() ) + return false; + + texture = std::move( it->second ); + mTextures.erase( it ); + } + + texture.reset(); + return true; +} + +void ResourceCatalog::clear() { + UnorderedMap textures; + { + Lock lock( mMutex ); + textures = std::move( mTextures ); + } + + textures.clear(); +} + +std::size_t ResourceCatalog::size() const { + Lock lock( mMutex ); + return mTextures.size(); +} + +}} // namespace EE::Graphics diff --git a/src/eepp/graphics/resourcescope.cpp b/src/eepp/graphics/resourcescope.cpp new file mode 100644 index 000000000..4100b4d5a --- /dev/null +++ b/src/eepp/graphics/resourcescope.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +using namespace EE::System; +using namespace EE::Window; + +namespace EE { namespace Graphics { + +ResourceScopePtr ResourceScope::New() { + return ResourceScopePtr( eeNew( ResourceScope, () ), ResourceDeleter() ); +} + +ResourceScope::ResourceScope() : mLocalCatalog( ResourceCatalog::New() ) {} + +TexturePtr ResourceScope::findTexture( const ResourceKey& key ) const { + return findTexture( key.value() ); +} + +TexturePtr ResourceScope::findTexture( const std::string& key ) const { + if ( TexturePtr texture = mLocalCatalog->findTexture( key ) ) + return texture; + + Lock lock( mMutex ); + for ( const ResourceCatalogPtr& catalog : mImports ) { + if ( TexturePtr texture = catalog->findTexture( key ) ) + return texture; + } + return {}; +} + +void ResourceScope::publishLocal( ResourceKey key, TexturePtr texture ) { + publishLocal( key.value(), std::move( texture ) ); +} + +void ResourceScope::publishLocal( std::string key, TexturePtr texture ) { + mLocalCatalog->publish( std::move( key ), std::move( texture ) ); +} + +bool ResourceScope::eraseLocal( const ResourceKey& key ) { + return mLocalCatalog->erase( key ); +} + +bool ResourceScope::eraseLocal( const std::string& key ) { + return mLocalCatalog->erase( key ); +} + +void ResourceScope::clearLocal() { + mLocalCatalog->clear(); +} + +void ResourceScope::importCatalog( ResourceCatalogPtr catalog ) { + if ( !catalog || catalog == mLocalCatalog ) + return; + + Lock lock( mMutex ); + if ( std::find( mImports.begin(), mImports.end(), catalog ) == mImports.end() ) + mImports.emplace_back( std::move( catalog ) ); +} + +bool ResourceScope::removeCatalog( const ResourceCatalogPtr& catalog ) { + ResourceCatalogPtr removed; + { + Lock lock( mMutex ); + auto it = std::find( mImports.begin(), mImports.end(), catalog ); + if ( it == mImports.end() ) + return false; + + removed = std::move( *it ); + mImports.erase( it ); + } + + removed.reset(); + return true; +} + +void ResourceScope::clearImports() { + std::vector imports; + { + Lock lock( mMutex ); + imports = std::move( mImports ); + } + + imports.clear(); +} + +ResourceCatalogPtr ResourceScope::getLocalCatalog() const { + return mLocalCatalog; +} + +ResourceCatalog& globalResourceCatalog() { + return *Engine::instance()->getGlobalResourceCatalog(); +} + +ResourceScope& defaultResourceScope() { + return *Engine::instance()->getDefaultResourceScope(); +} + +}} // namespace EE::Graphics diff --git a/src/eepp/graphics/textureatlasloader.cpp b/src/eepp/graphics/textureatlasloader.cpp index 03042a357..4c6db9f44 100644 --- a/src/eepp/graphics/textureatlasloader.cpp +++ b/src/eepp/graphics/textureatlasloader.cpp @@ -10,8 +10,11 @@ #include #include #include +#include #include +using namespace EE::Window; + namespace EE { namespace Graphics { using namespace Private; @@ -123,7 +126,18 @@ void TextureAtlasLoader::setTextureFilter( const Texture::Filter& textureFilter mTextureAtlas->getTexture( i )->setFilter( textureFilter ); } +void TextureAtlasLoader::setResourceScope( ResourceScopePtr resourceScope ) { + mResourceScope = std::move( resourceScope ); +} + +const ResourceScopePtr& TextureAtlasLoader::getResourceScope() const { + return mResourceScope; +} + void TextureAtlasLoader::loadFromStream( IOStream& IOS ) { + if ( !mResourceScope ) + mResourceScope = Engine::instance()->getDefaultResourceScope(); + mRL.setThreaded( mThreaded ); if ( IOS.isOpen() ) { @@ -143,22 +157,28 @@ void TextureAtlasLoader::loadFromStream( IOStream& IOS ) { std::string name( &tTextureHdr.Name[0] ); std::string path( FileSystem::fileRemoveFileName( mTextureAtlasPath ) + name ); + FileSystem::filePathRemoveProcessPath( path ); //! Checks if the texture is already loaded - TexturePtr tTex = TextureFactory::instance()->getByName( path ); + TexturePtr tTex = mResourceScope->findTexture( path ); tTexAtlas.LoadedTexture = tTex; if ( !mSkipResourceLoad && NULL == tTex ) { const std::size_t textureIndex = mTempAtlass.size(); if ( NULL != mPack ) { mRL.add( [this, textureIndex, path = std::move( path )] { - mTempAtlass[textureIndex].LoadedTexture = + TexturePtr texture = TextureFactory::instance()->loadFromPack( mPack, path ); + if ( texture ) + mResourceScope->publishLocal( path, texture ); + mTempAtlass[textureIndex].LoadedTexture = std::move( texture ); } ); } else { mRL.add( [this, textureIndex, path = std::move( path )] { - mTempAtlass[textureIndex].LoadedTexture = - TextureFactory::instance()->loadFromFile( path ); + TexturePtr texture = TextureFactory::instance()->loadFromFile( path ); + if ( texture ) + mResourceScope->publishLocal( path, texture ); + mTempAtlass[textureIndex].LoadedTexture = std::move( texture ); } ); } } diff --git a/src/eepp/graphics/texturefactory.cpp b/src/eepp/graphics/texturefactory.cpp index db12b0c66..fa166b428 100644 --- a/src/eepp/graphics/texturefactory.cpp +++ b/src/eepp/graphics/texturefactory.cpp @@ -32,7 +32,6 @@ const Texture::CoordinateType& TextureFactory::getLastCoordinateType() const { } TextureFactory::~TextureFactory() { - unloadTextures(); collectReleasedTextures(); diagnoseLiveTexturesAtShutdown(); } @@ -142,7 +141,6 @@ TexturePtr TextureFactory::pushTexture( const std::string& Filepath, const Uint3 Tex->create( textureHandle, Width, Height, ImgWidth, ImgHeight, Mipmap, Channels, FPath, ClampMode, CompressTexture, MemSize ); TextureWeakPtr weakTexture( texture ); - mTextures.emplace( resourceId.value(), texture ); mLiveTextures.emplace( resourceId.value(), LiveTextureRecord{ resourceId, std::move( weakTexture ) } ); mLiveTextureGeneration.fetch_add( 1, std::memory_order_release ); @@ -203,38 +201,6 @@ void TextureFactory::bind( ResourceId textureId, Texture::CoordinateType coordin bind( getTexture( textureId ).get(), coordinateType, textureUnit, forceRebind ); } -void TextureFactory::unloadTextures() { - TextureMap textures; - { - Lock l( *this ); - textures = std::move( mTextures ); - std::fill( mCurrentTexture.begin(), mCurrentTexture.end(), 0 ); - } - - // DrawableResource destruction emits callbacks, so release factory ownership without holding - // the registry/factory mutex. - textures.clear(); - - Log::debug( "Textures Unloaded." ); -} - -bool TextureFactory::releaseRetainedTexture( ResourceId textureId ) { - TexturePtr texture; - { - Lock l( *this ); - auto it = mTextures.find( textureId.value() ); - if ( it == mTextures.end() ) - return false; - - texture = std::move( it->second ); - mTextures.erase( it ); - resetTextureBinding( texture.get() ); - } - - texture.reset(); - return true; -} - void TextureFactory::resetTextureBinding( const Texture* texture ) { if ( !texture ) return; @@ -302,9 +268,6 @@ Uint64 TextureFactory::getLiveTextureGeneration() const { } void TextureFactory::queueReleasedTexture( Texture* texture ) { - eeASSERTM( Window::Engine::existsSingleton() && Window::Engine::isMainThread(), - Texture_final_release_must_run_on_the_graphics_thread ); - Lock l( *this ); mReleasedTextures.push_back( texture ); mLiveTextureGeneration.fetch_add( 1, std::memory_order_release ); @@ -382,53 +345,38 @@ unsigned int TextureFactory::getValidTextureSize( const unsigned int& Size ) { bool TextureFactory::existsId( ResourceId textureId ) { Lock l( *this ); - - return mTextures.find( textureId.value() ) != mTextures.end(); + auto it = mLiveTextures.find( textureId.value() ); + return it != mLiveTextures.end() && !it->second.texture.expired(); } TexturePtr TextureFactory::getTexture( ResourceId textureId ) { Lock l( *this ); - auto it = mTextures.find( textureId.value() ); - return it != mTextures.end() ? it->second : TexturePtr{}; -} - -TexturePtr TextureFactory::getByName( const std::string& Name ) { - return getByHash( String::hash( Name ) ); + auto it = mLiveTextures.find( textureId.value() ); + return it != mLiveTextures.end() ? it->second.texture.lock() : TexturePtr{}; } Uint32 TextureFactory::getTextureCount() { + purgeExpiredTextures(); Lock l( *this ); - - return (Uint32)mTextures.size(); + return static_cast( mLiveTextures.size() ); } unsigned int TextureFactory::getTextureMemorySize() { - Lock l( *this ); - - std::size_t memorySize = 0; - for ( const auto& texture : mLiveTextures ) { - if ( TexturePtr liveTexture = texture.second.texture.lock() ) - memorySize += liveTexture->getMemSize(); - } - return static_cast( memorySize ); -} - -TexturePtr TextureFactory::getByHash( const String::HashType& hash ) { - Lock l( *this ); - - Uint64 latestId = 0; - TexturePtr latestTexture; - for ( const auto& texture : mTextures ) { - const TexturePtr& tTex = texture.second; - - if ( NULL != tTex && texture.first > latestId && tTex->getHashName() == hash ) { - latestId = texture.first; - latestTexture = tTex; + std::vector liveTextures; + { + Lock l( *this ); + liveTextures.reserve( mLiveTextures.size() ); + for ( const auto& record : mLiveTextures ) { + if ( TexturePtr texture = record.second.texture.lock() ) + liveTextures.emplace_back( std::move( texture ) ); } } - return latestTexture; + std::size_t memorySize = 0; + for ( const TexturePtr& texture : liveTextures ) + memorySize += texture->getMemSize(); + return static_cast( memorySize ); } }} // namespace EE::Graphics diff --git a/src/eepp/graphics/textureloader.cpp b/src/eepp/graphics/textureloader.cpp index 72ab35565..5b0f6951b 100644 --- a/src/eepp/graphics/textureloader.cpp +++ b/src/eepp/graphics/textureloader.cpp @@ -83,9 +83,27 @@ TextureLoader::TextureLoader( const unsigned char* Pixels, const unsigned int& W TextureLoader::~TextureLoader() { eeSAFE_DELETE( mColorKey ); + freePixels(); +} - if ( TEX_LT_PIXELS != mLoadType ) - eeSAFE_FREE( mPixels ); +void TextureLoader::freePixels() { + if ( !mPixels ) { + mPixelsUseSystemFree = false; + return; + } + + if ( TEX_LT_PIXELS == mLoadType ) { + mPixels = nullptr; + mPixelsUseSystemFree = false; + return; + } + + if ( mPixelsUseSystemFree ) + ::free( mPixels ); + else + eeFree( mPixels ); + mPixels = nullptr; + mPixelsUseSystemFree = false; } void TextureLoader::load() { @@ -146,6 +164,7 @@ void TextureLoader::loadFromFile() { mFormatConfiguration ); image.avoidFreeImage( true ); mPixels = image.getPixels(); + mPixelsUseSystemFree = true; mImgWidth = image.getWidth(); mImgHeight = image.getHeight(); mChannels = image.getChannels(); @@ -200,6 +219,7 @@ void TextureLoader::loadFromMemory() { mFormatConfiguration ); image.avoidFreeImage( true ); mPixels = image.getPixels(); + mPixelsUseSystemFree = true; mImgWidth = image.getWidth(); mImgHeight = image.getHeight(); mChannels = image.getChannels(); @@ -257,6 +277,7 @@ void TextureLoader::loadFromStream() { mFormatConfiguration ); image.avoidFreeImage( true ); mPixels = image.getPixels(); + mPixelsUseSystemFree = true; mImgWidth = image.getWidth(); mImgHeight = image.getHeight(); mChannels = image.getChannels(); @@ -446,16 +467,7 @@ void TextureLoader::setFormatConfiguration( } void TextureLoader::reset() { - if ( mTexture ) { - if ( TextureFactory* factory = TextureFactory::existsSingleton() ) - factory->releaseRetainedTexture( mTexture->getTextureId() ); - } - - if ( TEX_LT_PIXELS != mLoadType ) { - eeSAFE_FREE( mPixels ); - } else { - mPixels = nullptr; - } + freePixels(); mTexture.reset(); mImgWidth = 0; mImgHeight = 0; diff --git a/src/eepp/ui/css/drawableimageparser.cpp b/src/eepp/ui/css/drawableimageparser.cpp index f17c1e440..67d40446e 100644 --- a/src/eepp/ui/css/drawableimageparser.cpp +++ b/src/eepp/ui/css/drawableimageparser.cpp @@ -166,7 +166,8 @@ Drawable* DrawableImageParser::createDrawable( const std::string& value, const S if ( exists( functionType.getName() ) ) return mFuncs[functionType.getName()]( functionType, size, ownIt, node ); } else if ( NULL != ( res = DrawableSearcher::searchByName( - value, false, node->getUISceneNode()->getReferer() ) ) ) { + value, false, node->getUISceneNode()->getReferer(), + node->getUISceneNode()->getResourceScope().get() ) ) ) { if ( res->getDrawableType() == Drawable::SPRITE ) ownIt = true; return res; @@ -926,7 +927,8 @@ void DrawableImageParser::registerBaseParsers() { !String::startsWith( param, "data:image/" ) ) { return DrawableSearcher::searchByName( node->getUISceneNode()->solveRelativePath( param ).toString(), false, - node->getUISceneNode()->getReferer() ); + node->getUISceneNode()->getReferer(), + node->getUISceneNode()->getResourceScope().get() ); } else if ( functionType.getParameters().size() > 1 && String::startsWith( param, "data:image/" ) ) { auto cparam = functionType.getParameters().at( 0 ); @@ -934,10 +936,12 @@ void DrawableImageParser::registerBaseParsers() { cparam += ','; cparam += functionType.getParameters().at( i ); } - return DrawableSearcher::searchByName( cparam, false, - node->getUISceneNode()->getReferer() ); + return DrawableSearcher::searchByName( + cparam, false, node->getUISceneNode()->getReferer(), + node->getUISceneNode()->getResourceScope().get() ); } - return DrawableSearcher::searchByName( param, false, node->getUISceneNode()->getReferer() ); + return DrawableSearcher::searchByName( param, false, node->getUISceneNode()->getReferer(), + node->getUISceneNode()->getResourceScope().get() ); }; mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size, bool&, diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 72017f08c..51924a47e 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -38,15 +38,19 @@ std::string getTextureCacheName( const Network::URI& uri ) { return filePath; } -TexturePtr loadFileTextureCached( const std::string& filePath, const std::string& cacheName ) { +TexturePtr loadFileTextureCached( const ResourceScopePtr& scope, const std::string& filePath, + const std::string& cacheName ) { static std::mutex loadMutex; std::lock_guard lock( loadMutex ); - if ( TexturePtr texture = TextureFactory::instance()->getByName( cacheName ) ) + if ( TexturePtr texture = scope->findTexture( cacheName ) ) return texture; - return TextureFactory::instance()->loadFromFile( + TexturePtr texture = TextureFactory::instance()->loadFromFile( filePath, false, Texture::ClampMode::ClampToEdge, false, false ); + if ( texture ) + scope->publishLocal( cacheName, texture ); + return texture; } } // namespace @@ -353,7 +357,8 @@ bool UIImage::loadFileDrawable( const Network::URI& uri ) { Uint64 loadId = ++mRemoteImageLoadId; std::string filePath = uri.getFSPath(); std::string cacheName = getTextureCacheName( uri ); - if ( TexturePtr texture = TextureFactory::instance()->getByName( cacheName ) ) { + ResourceScopePtr resourceScope = scene->getResourceScope(); + if ( TexturePtr texture = resourceScope->findTexture( cacheName ) ) { setDrawable( std::move( texture ) ); return true; } @@ -363,14 +368,14 @@ bool UIImage::loadFileDrawable( const Network::URI& uri ) { resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; auto alive = mAsyncImageAlive; - scene->getThreadPool()->run( [resourceState, resourceGeneration, alive, loadId, + scene->getThreadPool()->run( [resourceState, resourceGeneration, resourceScope, alive, loadId, filePath = std::move( filePath ), cacheName = std::move( cacheName ), this] { if ( !UISceneNode::isAsyncResourceLoadCurrent( resourceState, resourceGeneration ) || !alive || !alive->load( std::memory_order_acquire ) ) return; - TexturePtr texture = loadFileTextureCached( filePath, cacheName ); + TexturePtr texture = loadFileTextureCached( resourceScope, filePath, cacheName ); if ( texture == nullptr ) return; @@ -393,7 +398,8 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { return; std::string url = uri.toString(); - if ( TexturePtr texture = TextureFactory::instance()->getByName( url ) ) { + ResourceScopePtr resourceScope = scene->getResourceScope(); + if ( TexturePtr texture = resourceScope->findTexture( url ) ) { if ( mDrawable != texture.get() ) { ++mRemoteImageLoadId; setDrawable( std::move( texture ) ); @@ -408,8 +414,10 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { auto alive = mAsyncImageAlive; TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); - if ( texture ) + if ( texture ) { + resourceScope->publishLocal( url, texture ); setDrawable( texture ); + } Http::Request::FieldTable headers; if ( !scene->getReferer().empty() ) @@ -562,7 +570,8 @@ bool UIImage::applyProperty( const StyleSheetProperty& attribute ) { } else { Drawable* res = NULL; if ( NULL != ( res = DrawableSearcher::searchByName( - path, false, scene ? scene->getReferer() : URI() ) ) ) + path, false, scene ? scene->getReferer() : URI(), + scene ? scene->getResourceScope().get() : nullptr ) ) ) setDrawable( res, res->getDrawableType() == Drawable::SPRITE ); } break; diff --git a/src/eepp/ui/uinodedrawable.cpp b/src/eepp/ui/uinodedrawable.cpp index c9399343b..467ccd1e6 100644 --- a/src/eepp/ui/uinodedrawable.cpp +++ b/src/eepp/ui/uinodedrawable.cpp @@ -712,7 +712,8 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value return true; std::string url = uri.toString(); - if ( TexturePtr texture = TextureFactory::instance()->getByName( url ) ) { + ResourceScopePtr resourceScope = scene->getResourceScope(); + if ( TexturePtr texture = resourceScope->findTexture( url ) ) { if ( mDrawable != texture.get() ) { ++mRemoteDrawableLoadId; setDrawable( std::move( texture ) ); @@ -727,8 +728,10 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value auto alive = mAsyncDrawableAlive; TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); - if ( texture ) + if ( texture ) { + resourceScope->publishLocal( url, texture ); setDrawable( texture ); + } Http::Request::FieldTable headers; if ( !scene->getReferer().empty() ) diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index e59c57f81..3831d3f04 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -33,6 +33,7 @@ #define PUGIXML_HEADER_ONLY #include +using namespace EE::Graphics; using namespace EE::Network; namespace EE { namespace UI { @@ -140,6 +141,7 @@ UISceneNode::UISceneNode( EE::Window::Window* window ) : mUIThemeManager( UIThemeManager::New() ), mUIIconThemeManager( UIIconThemeManager::New()->setFallbackThemeManager( mUIThemeManager ) ), mAsyncResourceLoadState( std::make_shared() ), + mResourceScope( ResourceScope::New() ), mKeyBindings( mWindow->getInput() ) { // Reset size since the SceneNode already set it but needs to set the size from zero to emit // the required events to its children. @@ -750,6 +752,15 @@ void UISceneNode::setThreadPool( const std::shared_ptr& threadPool ) mThreadPool = threadPool; } +const ResourceScopePtr& UISceneNode::getResourceScope() const { + return mResourceScope; +} + +UISceneNode* UISceneNode::setResourceScope( ResourceScopePtr resourceScope ) { + mResourceScope = resourceScope ? std::move( resourceScope ) : ResourceScope::New(); + return this; +} + static std::string getErrorContext( size_t offset, std::string_view content ) { static constexpr std::size_t CONTEXT_LENGTH = 50; std::size_t minVal = offset >= CONTEXT_LENGTH ? offset - CONTEXT_LENGTH : 0; diff --git a/src/eepp/ui/uisprite.cpp b/src/eepp/ui/uisprite.cpp index 1c05aa4b0..91f34e117 100644 --- a/src/eepp/ui/uisprite.cpp +++ b/src/eepp/ui/uisprite.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace EE { namespace UI { @@ -238,7 +239,14 @@ bool UISprite::applyProperty( const StyleSheetProperty& attribute ) { } Drawable* res = NULL; - if ( NULL != ( res = DrawableSearcher::searchByName( path, true ) ) ) { + UISceneNode* scene = getUISceneNode(); + if ( scene ) + res = DrawableSearcher::searchByName( path, true, scene->getReferer(), + scene->getResourceScope().get() ); + else + res = DrawableSearcher::searchByName( path, true ); + + if ( NULL != res ) { setIsSpriteOwner( true ); if ( res->getDrawableType() == Drawable::SPRITE ) { diff --git a/src/eepp/window/engine.cpp b/src/eepp/window/engine.cpp index 983280783..d764069d8 100644 --- a/src/eepp/window/engine.cpp +++ b/src/eepp/window/engine.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,8 @@ #endif +using namespace EE::Graphics; + namespace EE { namespace Window { static UintPtr sMainThreadId{ 0 }; @@ -62,7 +65,10 @@ Engine::Engine() : mSharedGLContext( true ), mPlatformHelper( NULL ), mZip( NULL ), - mDisplayManager( NULL ) { + mDisplayManager( NULL ), + mGlobalResourceCatalog( ResourceCatalog::New() ), + mDefaultResourceScope( ResourceScope::New() ) { + mDefaultResourceScope->importCatalog( mGlobalResourceCatalog ); #if EE_PLATFORM == EE_PLATFORM_ANDROID mZip = Zip::New(); mZip->open( getPlatformHelper()->getApkPath() ); @@ -112,6 +118,11 @@ Engine::~Engine() { Graphics::Private::VertexBufferManager::destroySingleton(); + // Catalogs are the final intentional texture owners. Clear them while the factory and current + // graphics context are still available for deferred release collection. + mDefaultResourceScope.reset(); + mGlobalResourceCatalog.reset(); + if ( TextureFactory* textureFactory = TextureFactory::existsSingleton() ) textureFactory->collectReleasedTextures(); @@ -153,6 +164,14 @@ Engine::~Engine() { Log::destroySingleton(); } +std::shared_ptr Engine::getGlobalResourceCatalog() const { + return mGlobalResourceCatalog; +} + +std::shared_ptr Engine::getDefaultResourceScope() const { + return mDefaultResourceScope; +} + void Engine::destroy() { for ( auto& it : mWindows ) { eeSAFE_DELETE( it.second ); diff --git a/src/modules/maps/src/eepp/maps/tilemap.cpp b/src/modules/maps/src/eepp/maps/tilemap.cpp index 005b0c8b4..ddfbf3082 100644 --- a/src/modules/maps/src/eepp/maps/tilemap.cpp +++ b/src/modules/maps/src/eepp/maps/tilemap.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -124,11 +125,12 @@ void TileMap::createEmptyTile() { //! I create a texture representing an empty tile to render instead of rendering with primitives //! because is a lot faster, at least with NVIDIA GPUs. TextureFactory* TF = TextureFactory::instance(); + ResourceScope& resourceScope = defaultResourceScope(); std::string tileName( String::format( "maptile-%dx%d-%u", mTileSize.getWidth(), mTileSize.getHeight(), mGridLinesColor.getValue() ) ); - TexturePtr texture = TF->getByName( tileName ); + TexturePtr texture = resourceScope.findTexture( tileName ); if ( NULL == texture ) { Uint32 x, y; @@ -151,6 +153,8 @@ void TileMap::createEmptyTile() { mTileTex = TF->loadFromPixels( Img.getPixelsPtr(), Img.getWidth(), Img.getHeight(), Img.getChannels(), true, Texture::ClampMode::ClampToEdge, false, false, tileName ); + if ( mTileTex ) + resourceScope.publishLocal( tileName, mTileTex ); } else { mTileTex = std::move( texture ); } diff --git a/src/tests/unit_tests/resource_prerequisite_tests.cpp b/src/tests/unit_tests/resource_prerequisite_tests.cpp index d99b31590..ba8e2394f 100644 --- a/src/tests/unit_tests/resource_prerequisite_tests.cpp +++ b/src/tests/unit_tests/resource_prerequisite_tests.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -250,6 +252,7 @@ UTEST( ResourcePrerequisites, textureRegistryTracksStableIdentityAndMemoryWithou EXPECT_EQ( factory->getTextureMemorySize(), 4u * 3u * 4u + 4u ); retainedFirst.reset(); EXPECT_TRUE( firstWeak.expired() ); + EXPECT_TRUE( factory->getTexture( firstId ) == nullptr ); snapshot = factory->snapshotTextures(); EXPECT_TRUE( std::none_of( snapshot.begin(), snapshot.end(), @@ -267,6 +270,123 @@ UTEST( ResourcePrerequisites, textureRegistryTracksStableIdentityAndMemoryWithou Engine::destroySingleton(); } +UTEST( ResourcePrerequisites, resourceCatalogOwnsPublishedTextures ) { + EE::Window::Window* window = createLifecycleTestWindow( "Resource catalog ownership test" ); + TextureFactory* factory = TextureFactory::instance(); + ResourceCatalogPtr catalog = ResourceCatalog::New(); + TexturePtr texture = factory->createEmptyTexture( 1, 1 ); + ASSERT_TRUE( texture != nullptr ); + TextureWeakPtr weakTexture = texture; + + catalog->publish( "catalog-texture", texture ); + catalog->publish( "catalog-texture-alias", texture ); + texture.reset(); + EXPECT_FALSE( weakTexture.expired() ); + EXPECT_TRUE( catalog->findTexture( "catalog-texture" ) != nullptr ); + + EXPECT_TRUE( catalog->erase( "catalog-texture" ) ); + EXPECT_FALSE( weakTexture.expired() ); + EXPECT_TRUE( catalog->erase( "catalog-texture-alias" ) ); + EXPECT_TRUE( weakTexture.expired() ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 1 ) ); + window->display( false ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); + + catalog.reset(); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, resourceScopesResolveOnlyLocalAndExplicitlyImportedCatalogs ) { + EE::Window::Window* window = createLifecycleTestWindow( "Resource scope isolation test" ); + TextureFactory* factory = TextureFactory::instance(); + ResourceScopePtr firstScope = ResourceScope::New(); + ResourceScopePtr secondScope = ResourceScope::New(); + ResourceCatalogPtr sharedCatalog = ResourceCatalog::New(); + ResourceCatalogPtr laterCatalog = ResourceCatalog::New(); + TexturePtr first = factory->createEmptyTexture( 1, 1 ); + TexturePtr second = factory->createEmptyTexture( 1, 1 ); + TexturePtr shared = factory->createEmptyTexture( 1, 1 ); + TexturePtr later = factory->createEmptyTexture( 1, 1 ); + TexturePtr observedOnly = factory->createEmptyTexture( 1, 1, 4, Color::Transparent, false, + Texture::ClampMode::ClampToEdge, false, + false, "observed-only" ); + ASSERT_TRUE( first != nullptr ); + ASSERT_TRUE( second != nullptr ); + ASSERT_TRUE( shared != nullptr ); + ASSERT_TRUE( later != nullptr ); + ASSERT_TRUE( observedOnly != nullptr ); + + firstScope->publishLocal( "same-name", first ); + secondScope->publishLocal( "same-name", second ); + sharedCatalog->publish( "shared-name", shared ); + laterCatalog->publish( "shared-name", later ); + firstScope->importCatalog( sharedCatalog ); + firstScope->importCatalog( laterCatalog ); + + EXPECT_EQ( first.get(), firstScope->findTexture( "same-name" ).get() ); + EXPECT_EQ( second.get(), secondScope->findTexture( "same-name" ).get() ); + EXPECT_EQ( shared.get(), firstScope->findTexture( "shared-name" ).get() ); + EXPECT_TRUE( secondScope->findTexture( "shared-name" ) == nullptr ); + EXPECT_TRUE( firstScope->findTexture( "observed-only" ) == nullptr ); + EXPECT_TRUE( secondScope->findTexture( "observed-only" ) == nullptr ); + EXPECT_TRUE( firstScope->removeCatalog( sharedCatalog ) ); + EXPECT_EQ( later.get(), firstScope->findTexture( "shared-name" ).get() ); + EXPECT_FALSE( firstScope->removeCatalog( sharedCatalog ) ); + + TextureWeakPtr externallyRetainedWeak = first; + TexturePtr externallyRetained = first; + firstScope.reset(); + EXPECT_FALSE( externallyRetainedWeak.expired() ); + + first.reset(); + second.reset(); + shared.reset(); + later.reset(); + observedOnly.reset(); + externallyRetained.reset(); + secondScope.reset(); + sharedCatalog.reset(); + laterCatalog.reset(); + window->display( false ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, defaultResourceScopeImportsGlobalCatalog ) { + EE::Window::Window* window = createLifecycleTestWindow( "Default resource scope test" ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1 ); + ASSERT_TRUE( texture != nullptr ); + globalResourceCatalog().publish( "global-texture", texture ); + + EXPECT_EQ( texture.get(), defaultResourceScope().findTexture( "global-texture" ).get() ); + ResourceScopePtr isolatedScope = ResourceScope::New(); + EXPECT_TRUE( isolatedScope->findTexture( "global-texture" ) == nullptr ); + + texture.reset(); + isolatedScope.reset(); + window->display( false ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, uiScenesOwnIsolatedScopesThatCanBeSharedExplicitly ) { + EE::Window::Window* window = createLifecycleTestWindow( "UI scene resource scope test" ); + UISceneNode* firstScene = UISceneNode::New( window ); + UISceneNode* secondScene = UISceneNode::New( window ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1 ); + ASSERT_TRUE( texture != nullptr ); + + firstScene->getResourceScope()->publishLocal( "scene-texture", texture ); + EXPECT_TRUE( secondScene->getResourceScope()->findTexture( "scene-texture" ) == nullptr ); + secondScene->setResourceScope( firstScene->getResourceScope() ); + EXPECT_EQ( texture.get(), + secondScene->getResourceScope()->findTexture( "scene-texture" ).get() ); + + texture.reset(); + eeDelete( secondScene ); + eeDelete( firstScene ); + window->display( false ); + Engine::destroySingleton(); +} + UTEST( ResourcePrerequisites, pendingBatchRetainsTextureUntilDisplayCollection ) { EE::Window::Window* window = createLifecycleTestWindow( "Texture deferred release test" ); TextureFactory* factory = TextureFactory::instance(); @@ -310,6 +430,38 @@ UTEST( ResourcePrerequisites, pendingBatchRetainsTextureUntilDisplayCollection ) Engine::destroySingleton(); } +UTEST( ResourcePrerequisites, workerFinalReleaseDefersDestructionUntilDisplay ) { + EE::Window::Window* window = createLifecycleTestWindow( "Worker texture release test" ); + TextureFactory* factory = TextureFactory::instance(); + TexturePtr texture = factory->createEmptyTexture( 1, 1 ); + ASSERT_TRUE( texture != nullptr ); + TextureWeakPtr weakTexture = texture; + + std::thread worker( [texture = std::move( texture )]() mutable { texture.reset(); } ); + worker.join(); + + EXPECT_TRUE( weakTexture.expired() ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 1 ) ); + window->display( false ); + EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, workerFileTextureLoadReleasesDecoderPixelsCorrectly ) { + EE::Window::Window* window = createLifecycleTestWindow( "Worker file texture load test" ); + TexturePtr texture; + std::thread worker( [&texture] { + texture = TextureFactory::instance()->loadFromFile( Sys::getProcessPath() + + "../assets/atlases/bnb/007.png" ); + } ); + worker.join(); + ASSERT_TRUE( texture != nullptr ); + + texture.reset(); + window->display( false ); + Engine::destroySingleton(); +} + UTEST( ResourcePrerequisites, textureRegionRetainsItsTexture ) { EE::Window::Window* window = createLifecycleTestWindow( "Texture region ownership test" ); TextureFactory* factory = TextureFactory::instance(); diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index cad48def8..e4cfef37f 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -3825,6 +3825,7 @@ UTEST( UIBackground, RemoteImageReusesCachedTexture ) { TexturePtr cached = TextureFactory::instance()->createEmptyTexture( 8, 8, 4, Color::White, false, Texture::ClampMode::ClampToEdge, false, false, imageURL ); ASSERT_TRUE( cached != nullptr ); + sceneNode->getResourceScope()->publishLocal( imageURL, cached ); sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( @@ -5117,7 +5118,8 @@ UTEST( UIHTML, DeferredFileImageReusesCachedTexture ) { sceneNode->setURI( URI( "file://" + processPath ) ); URI imageURI = sceneNode->solveRelativePath( URI( "../assets/icon/ee.png" ) ); ASSERT_TRUE( FileSystem::fileExists( imageURI.getFSPath() ) ); - Drawable* cached = DrawableSearcher::searchByName( imageURI.toString() ); + Drawable* cached = DrawableSearcher::searchByName( + imageURI.toString(), false, sceneNode->getReferer(), sceneNode->getResourceScope().get() ); ASSERT_TRUE( cached != nullptr ); sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( @@ -5160,6 +5162,7 @@ UTEST( UIHTML, RemoteImageReusesCachedTexture ) { TexturePtr cached = TextureFactory::instance()->createEmptyTexture( 8, 8, 4, Color::White, false, Texture::ClampMode::ClampToEdge, false, false, imageURL ); ASSERT_TRUE( cached != nullptr ); + sceneNode->getResourceScope()->publishLocal( imageURL, cached ); sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( diff --git a/src/tools/ecode/settingsactions.cpp b/src/tools/ecode/settingsactions.cpp index 5cb80197d..959977147 100644 --- a/src/tools/ecode/settingsactions.cpp +++ b/src/tools/ecode/settingsactions.cpp @@ -117,11 +117,14 @@ void SettingsActions::aboutEcode() { UIImage* image = UIImage::New(); image->setParent( msgBox->getContainer()->getFirstChild() ); auto tf = TextureFactory::instance(); - TexturePtr tex = tf->getByName( "ecode-logo" ); + auto resourceScope = mApp->getUISceneNode()->getResourceScope(); + TexturePtr tex = resourceScope->findTexture( "ecode-logo" ); if ( tex == nullptr ) { tex = tf->loadFromFile( mApp->resPath() + "icon/ecode.png" ); - if ( tex ) + if ( tex ) { tex->setName( "ecode-logo" ); + resourceScope->publishLocal( "ecode-logo", tex ); + } } image->setDrawable( std::move( tex ) ); image->setLayoutGravity( UI_NODE_ALIGN_CENTER ); From dc377335b9c5e29230964e28d5906c863d4d324f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 20 Jul 2026 01:09:36 -0300 Subject: [PATCH 03/18] graphics: migrate drawables to shared ownership - introduce DrawablePtr and per-consumer createInstance() semantics - add TextureDrawable wrappers backed by shared TexturePtr handles - migrate regions, sprites, nine-patches, state lists, skins, groups, gradients, UI widgets, tools, modules, ecode, and eeiv to shared drawable ownership - remove ownIt flags, manual drawable deletion, and unload-based lifetime handling - replace drawable callback IDs with RAII change connections - move Variant drawable storage outside its scalar union - split UIIcon source lookup from explicit drawable instance creation - retain icon instances used by rendering loops instead of cloning per frame - avoid shared source mutation in texture-region consumers - use drawable type tags instead of RTTI casts - fix nearest-size UIIcon lookup - document the drawable source/instance contract and add ownership tests --- .../resource_shared_ownership_architecture.md | 74 ++++- include/eepp/graphics.hpp | 1 + include/eepp/graphics/arcdrawable.hpp | 2 + include/eepp/graphics/circledrawable.hpp | 2 + include/eepp/graphics/convexshapedrawable.hpp | 2 + include/eepp/graphics/drawable.hpp | 10 + include/eepp/graphics/drawablegroup.hpp | 15 +- include/eepp/graphics/drawableresource.hpp | 47 ++- include/eepp/graphics/drawablesearcher.hpp | 4 +- include/eepp/graphics/glyphdrawable.hpp | 2 + include/eepp/graphics/ninepatch.hpp | 4 +- include/eepp/graphics/rectangledrawable.hpp | 2 + include/eepp/graphics/resource.hpp | 4 + include/eepp/graphics/scrollparallax.hpp | 2 +- include/eepp/graphics/sprite.hpp | 33 ++- include/eepp/graphics/statelistdrawable.hpp | 8 +- include/eepp/graphics/texture.hpp | 2 + include/eepp/graphics/texturedrawable.hpp | 36 +++ include/eepp/graphics/textureregion.hpp | 5 + include/eepp/graphics/triangledrawable.hpp | 4 +- include/eepp/ui/css/drawableimageparser.hpp | 11 +- include/eepp/ui/lineargradientdrawable.hpp | 2 + include/eepp/ui/models/variant.hpp | 27 +- include/eepp/ui/radialgradientdrawable.hpp | 2 + include/eepp/ui/uicodeeditor.hpp | 16 +- include/eepp/ui/uiconsole.hpp | 2 +- include/eepp/ui/uiicon.hpp | 19 +- include/eepp/ui/uiimage.hpp | 15 +- include/eepp/ui/uimenu.hpp | 11 +- include/eepp/ui/uinode.hpp | 10 +- include/eepp/ui/uinodedrawable.hpp | 17 +- include/eepp/ui/uipushbutton.hpp | 2 +- include/eepp/ui/uiscenenode.hpp | 2 +- include/eepp/ui/uiskin.hpp | 6 +- include/eepp/ui/uiskinstate.hpp | 7 +- include/eepp/ui/uisprite.hpp | 20 +- include/eepp/ui/uitabwidget.hpp | 6 +- include/eepp/ui/uitextinput.hpp | 2 +- include/eepp/ui/uitextureregion.hpp | 6 +- src/eepp/graphics/arcdrawable.cpp | 12 + src/eepp/graphics/circledrawable.cpp | 14 + src/eepp/graphics/convexshapedrawable.cpp | 13 + src/eepp/graphics/drawable.cpp | 4 + src/eepp/graphics/drawablegroup.cpp | 57 ++-- src/eepp/graphics/drawableresource.cpp | 76 +++-- src/eepp/graphics/drawablesearcher.cpp | 68 +++-- src/eepp/graphics/glyphdrawable.cpp | 12 + src/eepp/graphics/ninepatch.cpp | 52 ++-- src/eepp/graphics/rectangledrawable.cpp | 15 + src/eepp/graphics/scrollparallax.cpp | 49 ++-- src/eepp/graphics/sprite.cpp | 134 ++++----- src/eepp/graphics/statelistdrawable.cpp | 75 ++--- src/eepp/graphics/texture.cpp | 11 + src/eepp/graphics/texturedrawable.cpp | 55 ++++ src/eepp/graphics/texturefactory.cpp | 5 + src/eepp/graphics/textureregion.cpp | 9 + src/eepp/graphics/triangledrawable.cpp | 16 + src/eepp/ui/abstract/uiabstracttableview.cpp | 8 +- src/eepp/ui/css/drawableimageparser.cpp | 155 +++++----- src/eepp/ui/lineargradientdrawable.cpp | 10 + src/eepp/ui/radialgradientdrawable.cpp | 36 ++- src/eepp/ui/tools/uicolorpicker.cpp | 11 +- src/eepp/ui/tools/uidiffview.cpp | 6 +- src/eepp/ui/tools/uifontpickerdialog.cpp | 14 +- src/eepp/ui/tools/uiimageviewer.cpp | 20 +- src/eepp/ui/tools/uitabwidgetsplitter.cpp | 2 +- src/eepp/ui/tools/uitextureviewer.cpp | 6 +- src/eepp/ui/tools/uiwidgetinspector.cpp | 4 +- src/eepp/ui/uicodeeditor.cpp | 51 ++-- src/eepp/ui/uiconsole.cpp | 6 +- src/eepp/ui/uifiledialog.cpp | 10 +- src/eepp/ui/uiicon.cpp | 49 ++-- src/eepp/ui/uiimage.cpp | 113 +++----- src/eepp/ui/uimenu.cpp | 27 +- src/eepp/ui/uimessagebox.cpp | 9 +- src/eepp/ui/uinode.cpp | 23 +- src/eepp/ui/uinodedrawable.cpp | 92 ++---- src/eepp/ui/uipushbutton.cpp | 19 +- src/eepp/ui/uiscenenode.cpp | 7 +- src/eepp/ui/uiskin.cpp | 34 ++- src/eepp/ui/uiskinstate.cpp | 9 +- src/eepp/ui/uisprite.cpp | 71 ++--- src/eepp/ui/uisvg.cpp | 31 +- src/eepp/ui/uitabwidget.cpp | 12 +- src/eepp/ui/uitextinput.cpp | 6 +- src/eepp/ui/uitextureregion.cpp | 76 ++--- src/eepp/ui/uitheme.cpp | 15 +- src/eepp/ui/uitreeview.cpp | 10 +- .../eterm/include/eterm/ui/uiterminal.hpp | 2 +- src/modules/eterm/src/eterm/ui/uiterminal.cpp | 6 +- .../include/eepp/maps/gameobjectsprite.hpp | 13 +- .../eepp/maps/gameobjecttextureregion.hpp | 2 +- .../include/eepp/maps/gameobjectvirtual.hpp | 2 +- .../maps/src/eepp/maps/gameobjectsprite.cpp | 19 +- .../src/eepp/maps/gameobjecttextureregion.cpp | 13 +- .../maps/src/eepp/maps/gameobjectvirtual.cpp | 26 +- .../src/eepp/maps/mapeditor/mapeditor.cpp | 10 +- .../eepp/physics/shapecirclesprite.hpp | 14 +- .../include/eepp/physics/shapepolysprite.hpp | 22 +- .../src/eepp/physics/shapecirclesprite.cpp | 15 +- .../src/eepp/physics/shapepolysprite.cpp | 25 +- src/tests/test_all/test.cpp | 15 +- src/tests/test_all/test.hpp | 4 +- .../unit_tests/drawableimageparser_tests.cpp | 129 +++------ .../resource_prerequisite_tests.cpp | 274 +++++++++++++++++- src/tests/unit_tests/uihtml_tests.cpp | 41 ++- src/tools/ecode/appconfig.cpp | 2 +- src/tools/ecode/appconfig.hpp | 2 +- src/tools/ecode/ecode.cpp | 18 +- src/tools/ecode/ecode.hpp | 4 +- .../ecode/plugins/aiassistant/chatui.cpp | 8 +- .../ecode/plugins/aiassistant/chatui.hpp | 2 +- .../autocomplete/autocompleteplugin.cpp | 23 +- .../autocomplete/autocompleteplugin.hpp | 2 + .../ecode/plugins/debugger/debuggerplugin.cpp | 47 ++- .../ecode/plugins/debugger/debuggerplugin.hpp | 6 + src/tools/ecode/plugins/git/gitplugin.cpp | 8 +- .../ecode/plugins/linter/linterplugin.cpp | 21 +- .../ecode/plugins/linter/linterplugin.hpp | 1 + .../ecode/plugins/lsp/lspclientplugin.cpp | 60 +++- .../ecode/plugins/lsp/lspclientplugin.hpp | 4 + src/tools/ecode/plugins/plugin.cpp | 4 +- src/tools/ecode/plugins/plugin.hpp | 2 +- .../ecode/plugins/plugincontextprovider.hpp | 4 +- .../spellchecker/spellcheckerplugin.cpp | 2 +- src/tools/ecode/projectbuild.cpp | 2 +- src/tools/ecode/settingsmenu.cpp | 2 +- src/tools/ecode/settingsmenu.hpp | 2 +- src/tools/ecode/statusterminalcontroller.cpp | 2 +- src/tools/uieditor/uieditor.cpp | 2 +- src/tools/uieditor/uieditor.hpp | 2 +- 131 files changed, 1747 insertions(+), 1178 deletions(-) create mode 100644 include/eepp/graphics/texturedrawable.hpp create mode 100644 src/eepp/graphics/texturedrawable.cpp diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index 45bdc5e74..3d75f9a27 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -1,7 +1,6 @@ # eepp shared-resource ownership architecture -Status: active implementation baseline; Stage 0 through Stage 3 complete; Stage 4 is next, -2026-07-19. +Status: active implementation baseline; Stage 0 through Stage 4 complete, 2026-07-20. This document freezes the contracts that must be true before the public texture API is changed. The implementation may refine names and small mechanics, but changing an invariant below requires an @@ -344,35 +343,73 @@ fixed: Shared lifetime and shareable instance state are separate concerns. `isStateful()` is not a sharing contract and will not be used as one. -### 7.1 Frozen source/instance split +### 7.1 Source/instance split Resource resolution caches immutable source data. UI consumers own per-consumer drawable instances: ```cpp -using DrawableSourcePtr = ResourcePtr; using DrawablePtr = ResourcePtr; -DrawableSourcePtr DrawableResolver::findSource( const DrawableRequest& request ); +DrawablePtr Drawable::createInstance() const; DrawablePtr DrawableResolver::createDrawable( const DrawableRequest& request ); ``` +Stage 4 established this contract without introducing a parallel `DrawableSource` class hierarchy. +Existing drawable resource types serve as source prototypes while retained by an atlas, theme, +icon, catalog, or resolver. A prototype is never handed directly to an unrelated consumer: +`createInstance()` returns independently mutable presentation state while sharing underlying +texture/resource handles. This is simpler than duplicating every drawable type into source and +instance classes and remains compatible with introducing immutable source-only types later when a +concrete resource requires one. + +eepp continues to use `Drawable::Type` for runtime drawable dispatch. Generic handle conversion +checks that tag and then uses `static_pointer_cast`; cloning code for a statically known concrete +type also uses `static_pointer_cast`. The ownership migration does not introduce RTTI casts. + Representative split: - `Texture` is shared GPU/resource data, not a globally shared mutable drawable instance. -- `TextureRegionSource` contains a TexturePtr, immutable source rectangle, offset, and intrinsic size. -- `NinePatchSource` contains immutable region and border data. -- `TextureDrawable`/`TextureRegionDrawable` hold per-consumer destination size, tint, alpha, position, - and other presentation state while retaining their source. +- `TextureRegion` prototypes and instances retain a TexturePtr; instances copy rectangle, offset, + intrinsic size, destination size, tint, and position. +- `NinePatch` instances clone their nine mutable region children while sharing the textures. +- `TextureDrawable` holds per-consumer destination size, tint, alpha, and position while retaining + the shared TexturePtr. - `StateListDrawable`, `DrawableGroup`, and `Sprite` are per-consumer state machines/instances that refer to source handles or private child instances. `DrawableImageParser::createDrawable()` always returns a fresh consumer instance for CSS-generated or resolved content, even when its immutable source came from a cache. +`UIIcon`, `UIGlyphIcon`, and `UISVGIcon` expose the split directly: + +```cpp +const DrawablePtr& UIIcon::getSource( int size ) const; +DrawablePtr UIIcon::createDrawable( int size ) const; +``` + +`getSource()` supports lookup and measurement without cloning an existing prototype. Glyph and SVG +icons may materialize and cache a missing size source once. `createDrawable()` is the explicit +consumer-instance boundary. Callers retain its result for as long as they render that icon. + The migration will remove draw-time mutation of shared child/source objects. Rendering APIs may use external draw parameters where that simplifies an implementation, but no shared source can be temporarily recolored, resized, repositioned, or advanced by a consumer. +No rendering callback may call `createInstance()`, `UIIcon::createDrawable()`, or an API that +performs either operation internally. Rendered instances are prepared during assignment, +deserialization, style/resource resolution, scheduled update, or plugin update and retained by the +consumer. The Stage 4 call-site audit classifies all remaining direct `createInstance()` calls as: + +- implementations recursively cloning their private child state; +- constructors and setters adopting a private region/sprite/map instance; +- theme, skin, icon, CSS, and name-resolution source-to-instance boundaries; +- widget deserialization and one-time assignment; or +- focused ownership tests. + +The code editor lock icon and ecode debugger, linter, LSP breadcrumb, and autocomplete icon paths +retain instances populated before drawing. Draw callbacks only look up and render those retained +instances; a cache miss skips the icon for that frame instead of cloning while rendering. + ### 7.2 Consumer API Consumers store a strong per-consumer instance: @@ -645,6 +682,25 @@ Exit criteria: ### Stage 4: drawable source/instance conversion +Status: complete, 2026-07-20. Drawable ownership +now uses `DrawablePtr`; textures create private +`TextureDrawable` wrappers; mutable prototypes implement `createInstance()`; sprites, state lists, +skins, groups, nine-patches, regions, glyphs, gradients, and primitive drawables clone their +presentation state. UIImage, UINodeDrawable, menus/icons/themes, parsers, editor/tool consumers, +maps, physics, ecode, and eeiv were migrated in the same API cut. + +`DrawableResource::Unload` and callback IDs were replaced by Change-only RAII connections. +`Variant` stores DrawablePtr outside its scalar union. UITextureRegion and ScrollParallax render +with local geometry rather than temporarily resizing shared source regions; region-based map +objects retain private instances. `DrawableSearcher` already returns fresh instances as a safe +bridge, but its replacement by the layered UI resolver remains Stage 5. + +`UIIcon::getSource()` now returns a cached source/prototype for lookup and measurement, while +`UIIcon::createDrawable()` explicitly creates one private consumer instance. `UIGlyphIcon` and +`UISVGIcon` cache their lazily materialized sources under the same contract. The complete +`createInstance()` call-site audit found no remaining render-loop cloning; previously hot code +editor, debugger, linter, LSP breadcrumb, and autocomplete paths retain update-time instances. + Introduce source types and per-consumer instances, remove shared draw-state mutation, replace manual ownership with DrawablePtr, remove Unload lifetime callbacks, add RAII change connections, and migrate Variant's storage. diff --git a/include/eepp/graphics.hpp b/include/eepp/graphics.hpp index 52cc62dc9..c3a8a5dc3 100644 --- a/include/eepp/graphics.hpp +++ b/include/eepp/graphics.hpp @@ -65,6 +65,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eepp/graphics/arcdrawable.hpp b/include/eepp/graphics/arcdrawable.hpp index 470c0103b..7b3d5b185 100644 --- a/include/eepp/graphics/arcdrawable.hpp +++ b/include/eepp/graphics/arcdrawable.hpp @@ -30,6 +30,8 @@ class EE_API ArcDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + Float getRadius() const; void setRadius( const Float& radius ); diff --git a/include/eepp/graphics/circledrawable.hpp b/include/eepp/graphics/circledrawable.hpp index a85071ed9..0f3523123 100644 --- a/include/eepp/graphics/circledrawable.hpp +++ b/include/eepp/graphics/circledrawable.hpp @@ -14,6 +14,8 @@ class EE_API CircleDrawable : public ArcDrawable { CircleDrawable(); CircleDrawable( const Float& radius, const Uint32& segmentsCount ); + + DrawablePtr createInstance() const; }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/convexshapedrawable.hpp b/include/eepp/graphics/convexshapedrawable.hpp index fc945faff..cc5601c05 100644 --- a/include/eepp/graphics/convexshapedrawable.hpp +++ b/include/eepp/graphics/convexshapedrawable.hpp @@ -24,6 +24,8 @@ class EE_API ConvexShapeDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + void setPolygon( const Polygon2f& polygon ); void addPoint( const Vector2f& point ); diff --git a/include/eepp/graphics/drawable.hpp b/include/eepp/graphics/drawable.hpp index 2b5e4cfd8..5b33592ca 100644 --- a/include/eepp/graphics/drawable.hpp +++ b/include/eepp/graphics/drawable.hpp @@ -2,6 +2,7 @@ #define EE_GRAPHICS_DRAWABLE_HPP #include +#include #include #include #include @@ -10,12 +11,17 @@ using namespace EE::System; namespace EE { namespace Graphics { +class Drawable; class StatefulDrawable; +using DrawablePtr = ResourcePtr; +using DrawableWeakPtr = ResourceWeakPtr; + class EE_API Drawable { public: enum Type { TEXTURE, + TEXTUREDRAWABLE, TEXTUREREGION, SPRITE, ARC, @@ -57,6 +63,10 @@ class EE_API Drawable { virtual bool isStateful() = 0; + /** Creates an independently mutable instance backed by the same immutable resource data. + * This is an ownership/setup operation; rendering loops must retain and reuse the result. */ + virtual DrawablePtr createInstance() const; + void setAlpha( Uint8 alpha ); const Uint8& getAlpha(); diff --git a/include/eepp/graphics/drawablegroup.hpp b/include/eepp/graphics/drawablegroup.hpp index cbdc45d01..a214f762a 100644 --- a/include/eepp/graphics/drawablegroup.hpp +++ b/include/eepp/graphics/drawablegroup.hpp @@ -8,7 +8,7 @@ namespace EE { namespace Graphics { class EE_API DrawableGroup : public Drawable { public: - static DrawableGroup* New(); + static ResourcePtr New(); DrawableGroup(); @@ -26,9 +26,11 @@ class EE_API DrawableGroup : public Drawable { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + void clearDrawables(); - Drawable* addDrawable( Drawable* drawable ); + DrawablePtr addDrawable( DrawablePtr drawable ); Uint32 getDrawableCount() const; @@ -36,19 +38,14 @@ class EE_API DrawableGroup : public Drawable { void setClipEnabled( bool clipEnabled ); - bool isDrawableOwner() const; - - void setDrawableOwner( bool drawableOwner ); - - std::vector& getGroup(); + std::vector& getGroup(); protected: - std::vector mGroup; + std::vector mGroup; std::vector mPos; Sizef mSize; bool mNeedsUpdate; bool mClipEnabled; - bool mDrawableOwner; virtual void onPositionChange(); diff --git a/include/eepp/graphics/drawableresource.hpp b/include/eepp/graphics/drawableresource.hpp index e68a48194..e0aabac64 100644 --- a/include/eepp/graphics/drawableresource.hpp +++ b/include/eepp/graphics/drawableresource.hpp @@ -3,16 +3,43 @@ #include #include +#include namespace EE { namespace Graphics { +class DrawableResource; + +struct DrawableResourceCallbackState { + using Callback = std::function; + Uint32 nextId{ 0 }; + UnorderedMap callbacks; +}; + +class EE_API DrawableResourceConnection { + public: + DrawableResourceConnection() = default; + ~DrawableResourceConnection(); + DrawableResourceConnection( DrawableResourceConnection&& other ) noexcept; + DrawableResourceConnection& operator=( DrawableResourceConnection&& other ) noexcept; + DrawableResourceConnection( const DrawableResourceConnection& ) = delete; + DrawableResourceConnection& operator=( const DrawableResourceConnection& ) = delete; + + void disconnect(); + explicit operator bool() const; + + private: + friend class DrawableResource; + DrawableResourceConnection( std::weak_ptr state, Uint32 id ); + + std::weak_ptr mState; + Uint32 mId{ 0 }; +}; + class EE_API DrawableResource : public Drawable { public: - enum Event { Change, Unload }; - virtual ~DrawableResource(); - typedef std::function OnResourceChangeCallback; + using OnResourceChangeCallback = DrawableResourceCallbackState::Callback; /** @return The DrawableResource Id. The Id is the String::hash of the name. */ const String::HashType& getId() const; @@ -26,19 +53,13 @@ class EE_API DrawableResource : public Drawable { /** Always true */ bool isDrawableResource() const; - /** Push a new on resource change callback. - * @return The Callback Id - */ - Uint32 pushResourceChangeCallback( const OnResourceChangeCallback& cb ); - - /** Pop the on resource change callback id indicated. */ - bool popResourceChangeCallback( const Uint32& callbackId ); + /** Connects a callback for mutable resource data changes. */ + DrawableResourceConnection connectResourceChange( OnResourceChangeCallback cb ); protected: std::string mName; String::HashType mId; - Uint32 mNumCallBacks; - UnorderedMap mCallbacks; + std::shared_ptr mCallbackState; explicit DrawableResource( Type drawableType ); @@ -48,7 +69,7 @@ class EE_API DrawableResource : public Drawable { virtual void onResourceChange(); - void sendEvent( const Event& event ); + void sendResourceChanged(); }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/drawablesearcher.hpp b/include/eepp/graphics/drawablesearcher.hpp index b42d1e20c..c95ea9cb4 100644 --- a/include/eepp/graphics/drawablesearcher.hpp +++ b/include/eepp/graphics/drawablesearcher.hpp @@ -11,11 +11,11 @@ class ResourceScope; class EE_API DrawableSearcher { public: - static Drawable* searchByName( const std::string& name, bool firstSearchSprite = false, + static DrawablePtr searchByName( const std::string& name, bool firstSearchSprite = false, Network::URI referer = "", ResourceScope* resourceScope = nullptr ); - static Drawable* searchById( const Uint32& id ); + static DrawablePtr searchById( const Uint32& id ); static void setPrintWarnings( const bool& print ); diff --git a/include/eepp/graphics/glyphdrawable.hpp b/include/eepp/graphics/glyphdrawable.hpp index c399fa893..653f6d941 100644 --- a/include/eepp/graphics/glyphdrawable.hpp +++ b/include/eepp/graphics/glyphdrawable.hpp @@ -35,6 +35,8 @@ class EE_API GlyphDrawable : public DrawableResource { virtual bool isStateful(); + DrawablePtr createInstance() const; + /** @return The texture instance used by the GlyphDrawable. */ const TexturePtr& getTexture() const; diff --git a/include/eepp/graphics/ninepatch.hpp b/include/eepp/graphics/ninepatch.hpp index e81f0af1d..d2041fd12 100644 --- a/include/eepp/graphics/ninepatch.hpp +++ b/include/eepp/graphics/ninepatch.hpp @@ -51,10 +51,12 @@ class EE_API NinePatch : public DrawableResource { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + TextureRegion* getTextureRegion( const int& side ); protected: - TextureRegion* mDrawable[SideCount]; + TextureRegionPtr mDrawable[SideCount]; Rect mRect; Rectf mRectf; Sizef mSize; diff --git a/include/eepp/graphics/rectangledrawable.hpp b/include/eepp/graphics/rectangledrawable.hpp index 933b65349..9f951082a 100644 --- a/include/eepp/graphics/rectangledrawable.hpp +++ b/include/eepp/graphics/rectangledrawable.hpp @@ -28,6 +28,8 @@ class EE_API RectangleDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + Float getRotation() const; void setRotation( const Float& rotation ); diff --git a/include/eepp/graphics/resource.hpp b/include/eepp/graphics/resource.hpp index afd5979a3..8473e92cd 100644 --- a/include/eepp/graphics/resource.hpp +++ b/include/eepp/graphics/resource.hpp @@ -51,6 +51,10 @@ template struct ResourceDeleter { void operator()( T* resource ) const noexcept { eeDelete( resource ); } }; +template ResourcePtr makeResource( Args&&... args ) { + return ResourcePtr( eeNew( T, ( std::forward( args )... ) ), ResourceDeleter() ); +} + }} // namespace EE::Graphics #endif diff --git a/include/eepp/graphics/scrollparallax.hpp b/include/eepp/graphics/scrollparallax.hpp index 034f28b2b..40c92e13a 100644 --- a/include/eepp/graphics/scrollparallax.hpp +++ b/include/eepp/graphics/scrollparallax.hpp @@ -89,7 +89,7 @@ class EE_API ScrollParallax { const Vector2f& getSpeed() const; private: - TextureRegion* mTextureRegion; + TextureRegionPtr mTextureRegion; BlendMode mBlend; Color mColor; Vector2f mInitPos; diff --git a/include/eepp/graphics/sprite.hpp b/include/eepp/graphics/sprite.hpp index 38dc9232d..899f7a80a 100644 --- a/include/eepp/graphics/sprite.hpp +++ b/include/eepp/graphics/sprite.hpp @@ -11,6 +11,9 @@ using namespace EE::System; namespace EE { namespace Graphics { +class Sprite; +using SpritePtr = ResourcePtr; + /** @brief A Sprite controller class, can hold and control sprites animations. */ class EE_API Sprite : public Drawable { public: @@ -26,22 +29,24 @@ class EE_API Sprite : public Drawable { SPRITE_EVENT_USER // User vents }; - static Sprite* New(); + static SpritePtr New(); - static Sprite* New( const std::string& name, const std::string& extension = "", + static SpritePtr New( const std::string& name, const std::string& extension = "", TextureAtlas* SearchInTextureAtlas = NULL ); - static Sprite* New( TextureRegion* TextureRegion ); + static SpritePtr New( TextureRegion* TextureRegion ); - static Sprite* New( ResourceId textureId, const Sizef& DestSize = Sizef( 0, 0 ), + static SpritePtr New( ResourceId textureId, const Sizef& DestSize = Sizef( 0, 0 ), const Vector2i& offset = Vector2i( 0, 0 ), const Rect& TexSector = Rect( 0, 0, 0, 0 ) ); - static Sprite* fromGif( IOStream& gif ); + static SpritePtr fromGif( IOStream& gif ); /** Instantiate an empty sprite */ Sprite(); + Sprite( const Sprite& other ); + /** Creates an animated Sprite from a animation name. It will search for a pattern name. * For example search for name "car" with extensions "png", i will try to find car00.png * car01.png car02.png, and so on, it will continue if find something, otherwise it will stop ( @@ -312,6 +317,8 @@ class EE_API Sprite : public Drawable { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + /** Set the number of repetitions of the animation. Any number below 0 the animation will loop. */ void setRepetitions( const int& Repeations ); @@ -379,8 +386,8 @@ class EE_API Sprite : public Drawable { /** Pop the event callback id indicated. */ bool popEventsCallback( const Uint32& callbackId ); - /** Creates a copy of the current sprite and returns it */ - Sprite clone(); + /** Creates an independent instance sharing the same texture resources. */ + SpritePtr clone() const; /** Update the sprite animation */ void update( const Time& ElapsedTime ); @@ -391,10 +398,6 @@ class EE_API Sprite : public Drawable { /** Fire a User Event in the sprite */ void fireEvent( const Uint32& Event ); - Sprite& setAsTextureRegionOwner( bool set ); - - bool isTextureRegionOwner() const; - protected: enum SpriteFlags { SPRITE_FLAG_AUTO_ANIM = ( 1 << 0 ), @@ -402,7 +405,6 @@ class EE_API Sprite : public Drawable { SPRITE_FLAG_ANIM_PAUSED = ( 1 << 2 ), SPRITE_FLAG_ANIM_TO_FRAME_AND_STOP = ( 1 << 3 ), SPRITE_FLAG_EVENTS_ENABLED = ( 1 << 4 ), - SPRITE_FLAG_TEXTURE_REGION_OWNER = ( 1 << 6 ), }; Uint32 mFlags{ SPRITE_FLAG_AUTO_ANIM | SPRITE_FLAG_EVENTS_ENABLED }; @@ -433,7 +435,7 @@ class EE_API Sprite : public Drawable { UnorderedMap mCallbacks; struct Frame { - std::vector Spr; + std::vector Spr; }; std::vector mFrames; @@ -441,11 +443,12 @@ class EE_API Sprite : public Drawable { void clearFrame(); - void cleanUpResources(); - unsigned int getFrame( const unsigned int& FrameNum ); unsigned int getSubFrame( const unsigned int& SubFrame ); + + bool addSubFrame( TextureRegionPtr textureRegion, const unsigned int& numFrame, + const unsigned int& numSubFrame ); }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/statelistdrawable.hpp b/include/eepp/graphics/statelistdrawable.hpp index 9b2c50371..7912581de 100644 --- a/include/eepp/graphics/statelistdrawable.hpp +++ b/include/eepp/graphics/statelistdrawable.hpp @@ -28,14 +28,15 @@ class EE_API StateListDrawable : public StatefulDrawable { virtual bool isStateful(); + DrawablePtr createInstance() const; + virtual StatefulDrawable* setState( Uint32 state ); virtual const Uint32& getState() const; virtual Drawable* getStateDrawable( const Uint32& state ); - virtual StateListDrawable* setStateDrawable( const Uint32& state, Drawable* drawable, - bool ownIt = false ); + virtual StateListDrawable* setStateDrawable( const Uint32& state, DrawablePtr drawable ); virtual Sizef getStateSize( const Uint32& state ); @@ -56,8 +57,7 @@ class EE_API StateListDrawable : public StatefulDrawable { protected: Uint32 mCurrentState; Drawable* mCurrentDrawable; - std::map mDrawables; - std::map mDrawablesOwnership; + std::map mDrawables; std::map mDrawableColors; explicit StateListDrawable( const std::string& name = "" ); diff --git a/include/eepp/graphics/texture.hpp b/include/eepp/graphics/texture.hpp index 676a094b6..4bc18c46d 100644 --- a/include/eepp/graphics/texture.hpp +++ b/include/eepp/graphics/texture.hpp @@ -294,6 +294,8 @@ class EE_API Texture : public DrawableResource, public Image, private NonCopyabl virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + /** @return The process-wide identity assigned to this texture. */ ResourceId getTextureId() const; diff --git a/include/eepp/graphics/texturedrawable.hpp b/include/eepp/graphics/texturedrawable.hpp new file mode 100644 index 000000000..527ae3f13 --- /dev/null +++ b/include/eepp/graphics/texturedrawable.hpp @@ -0,0 +1,36 @@ +#ifndef EE_GRAPHICS_TEXTUREDRAWABLE_HPP +#define EE_GRAPHICS_TEXTUREDRAWABLE_HPP + +#include +#include + +namespace EE { namespace Graphics { + +class TextureDrawable; +using TextureDrawablePtr = ResourcePtr; + +/** Per-consumer drawable state backed by a shared texture resource. */ +class EE_API TextureDrawable : public DrawableResource { + public: + static TextureDrawablePtr New( TexturePtr texture ); + + explicit TextureDrawable( TexturePtr texture ); + + Sizef getSize(); + Sizef getPixelsSize(); + void draw(); + void draw( const Vector2f& position ); + void draw( const Vector2f& position, const Sizef& size ); + bool isStateful(); + DrawablePtr createInstance() const; + + const TexturePtr& getTexture() const; + + protected: + TexturePtr mTexture; + DrawableResourceConnection mTextureChangeConnection; +}; + +}} // namespace EE::Graphics + +#endif diff --git a/include/eepp/graphics/textureregion.hpp b/include/eepp/graphics/textureregion.hpp index 7ac5f3941..6342b4d79 100644 --- a/include/eepp/graphics/textureregion.hpp +++ b/include/eepp/graphics/textureregion.hpp @@ -8,6 +8,9 @@ namespace EE { namespace Graphics { +class TextureRegion; +using TextureRegionPtr = ResourcePtr; + /** @brief A TextureRegion is a part of a texture that represent an sprite.*/ class EE_API TextureRegion : public DrawableResource { public: @@ -126,6 +129,8 @@ class EE_API TextureRegion : public DrawableResource { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + /** @return The texture instance used by the TextureRegion. */ const TexturePtr& getTexture() const; diff --git a/include/eepp/graphics/triangledrawable.hpp b/include/eepp/graphics/triangledrawable.hpp index 5c3560018..c5b1c67e9 100644 --- a/include/eepp/graphics/triangledrawable.hpp +++ b/include/eepp/graphics/triangledrawable.hpp @@ -28,6 +28,8 @@ class EE_API TriangleDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } + DrawablePtr createInstance() const; + void setSize( const Sizef& size ); const Triangle2f& getTriangle() const; @@ -41,7 +43,7 @@ class EE_API TriangleDrawable : public PrimitiveDrawable { Triangle2f mComputedTriangle; Sizef mSize; Color mColors[3]; - bool mCustomColors; + bool mCustomColors{ false }; virtual void onColorFilterChange(); diff --git a/include/eepp/ui/css/drawableimageparser.hpp b/include/eepp/ui/css/drawableimageparser.hpp index 5e9160a45..d70809597 100644 --- a/include/eepp/ui/css/drawableimageparser.hpp +++ b/include/eepp/ui/css/drawableimageparser.hpp @@ -2,14 +2,12 @@ #define EE_UI_CSS_DRAWABLEIMAGEPARSER_HPP #include +#include #include #include #include #include -namespace EE { namespace Graphics { -class Drawable; -}} // namespace EE::Graphics namespace EE { namespace UI { class UINode; }} // namespace EE::UI @@ -20,8 +18,8 @@ using namespace EE::System; namespace EE { namespace UI { namespace CSS { -typedef std::function +typedef std::function DrawableImageParserFunc; class EE_API DrawableImageParser { @@ -30,8 +28,7 @@ class EE_API DrawableImageParser { bool exists( const std::string& name ) const; - Drawable* createDrawable( const std::string& value, const Sizef& size, bool& ownIt, - UINode* node ); + DrawablePtr createDrawable( const std::string& value, const Sizef& size, UINode* node ); void addParser( const std::string& name, const DrawableImageParserFunc& func ); diff --git a/include/eepp/ui/lineargradientdrawable.hpp b/include/eepp/ui/lineargradientdrawable.hpp index 5f2ea908f..4b28292d5 100644 --- a/include/eepp/ui/lineargradientdrawable.hpp +++ b/include/eepp/ui/lineargradientdrawable.hpp @@ -50,6 +50,8 @@ class EE_API LinearGradientDrawable : public Graphics::Drawable { virtual bool isStateful() { return false; } + Graphics::DrawablePtr createInstance() const; + const std::vector& getColorStops() const; void setColorStops( std::vector stops ); diff --git a/include/eepp/ui/models/variant.hpp b/include/eepp/ui/models/variant.hpp index 4c47e67c0..15d30c646 100644 --- a/include/eepp/ui/models/variant.hpp +++ b/include/eepp/ui/models/variant.hpp @@ -43,10 +43,7 @@ class EE_API Variant { explicit Variant( const String* string ) : mType( Type::StringPtr ) { mValue.asStringPtr = string; } - Variant( Drawable* drawable, bool ownDrawable = false ) : mType( Type::Drawable ) { - mValue.asDrawable = drawable; - mOwnsObject = ownDrawable; - } + Variant( DrawablePtr drawable ) : mDrawable( std::move( drawable ) ), mType( Type::Drawable ) {} Variant( UIIcon* icon ) : mType( Type::Icon ) { mValue.asIcon = icon; } Variant( const Vector2f& v ) : mType( Type::Vector2f ) { mValue.asVector2f = eeNew( Vector2f, ( v ) ); @@ -62,7 +59,7 @@ class EE_API Variant { explicit Variant( const char* data ) : mType( Type::cstr ) { mValue.asCStr = data; } ~Variant() { reset(); } - Variant( const Variant& other ) : mType( Type::Invalid ), mOwnsObject( other.mOwnsObject ) { + Variant( const Variant& other ) : mType( Type::Invalid ) { switch ( other.mType ) { case Type::StdString: mValue.asStdString = eeNew( std::string, ( *other.mValue.asStdString ) ); @@ -74,7 +71,7 @@ class EE_API Variant { mValue.asStringPtr = other.mValue.asStringPtr; break; case Type::Drawable: - mValue.asDrawable = other.mValue.asDrawable; + mDrawable = other.mDrawable; break; case Type::Icon: mValue.asIcon = other.mValue.asIcon; @@ -116,9 +113,8 @@ class EE_API Variant { } Variant( Variant&& other ) noexcept : - mValue( other.mValue ), mType( other.mType ), mOwnsObject( other.mOwnsObject ) { + mValue( other.mValue ), mDrawable( std::move( other.mDrawable ) ), mType( other.mType ) { other.mType = Type::Invalid; - other.mOwnsObject = false; other.mValue = {}; } @@ -134,10 +130,9 @@ class EE_API Variant { if ( this != &other ) { reset(); mType = other.mType; - mOwnsObject = other.mOwnsObject; + mDrawable = std::move( other.mDrawable ); mValue = other.mValue; other.mType = Type::Invalid; - other.mOwnsObject = false; other.mValue = {}; } return *this; @@ -146,7 +141,7 @@ class EE_API Variant { const std::string& asStdString() const { return *mValue.asStdString; } const String& asString() const { return *mValue.asString; } const String& asStringPtr() const { return *mValue.asStringPtr; } - Drawable* asDrawable() const { return mValue.asDrawable; } + const DrawablePtr& asDrawable() const { return mDrawable; } const bool& asBool() const { return mValue.asBool; } const Float& asFloat() const { return mValue.asFloat; } const int& asInt() const { return mValue.asInt; } @@ -172,8 +167,7 @@ class EE_API Variant { eeSAFE_DELETE( mValue.asString ); break; case Type::Drawable: - if ( mOwnsObject ) - eeSAFE_DELETE( mValue.asDrawable ); + mDrawable.reset(); break; case Type::Vector2f: eeSAFE_DELETE( mValue.asVector2f ); @@ -210,7 +204,7 @@ class EE_API Variant { return asStringPtr(); case Type::Drawable: return asDrawable()->isDrawableResource() - ? static_cast( asDrawable() )->getName() + ? static_cast( asDrawable().get() )->getName() : "Drawable"; case Type::Icon: return asIcon()->getName(); @@ -330,7 +324,7 @@ class EE_API Variant { case Type::StringPtr: return asStringPtr().size(); case Type::Drawable: - return sizeof( mValue.asDrawable ); + return sizeof( mDrawable ); case Type::Icon: return asIcon()->getName().size(); case Type::DataPtr: @@ -350,7 +344,6 @@ class EE_API Variant { private: union { void* asDataPtr{ nullptr }; - Drawable* asDrawable; UIIcon* asIcon; std::string* asStdString; String* asString; @@ -365,8 +358,8 @@ class EE_API Variant { Rectf* asRectf; const char* asCStr; } mValue; + DrawablePtr mDrawable; Type mType; - bool mOwnsObject{ false }; }; }}} // namespace EE::UI::Models diff --git a/include/eepp/ui/radialgradientdrawable.hpp b/include/eepp/ui/radialgradientdrawable.hpp index 1a9b94b83..626a92f7c 100644 --- a/include/eepp/ui/radialgradientdrawable.hpp +++ b/include/eepp/ui/radialgradientdrawable.hpp @@ -54,6 +54,8 @@ class EE_API RadialGradientDrawable : public Graphics::Drawable { virtual bool isStateful() { return false; } + Graphics::DrawablePtr createInstance() const; + const std::vector& getColorStops() const; void setColorStops( std::vector stops ); diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index bce99ca02..6529e7512 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -768,13 +768,13 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void setShowFoldingRegion( bool showFoldingRegion ); - Drawable* getFoldDrawable() const; + const DrawablePtr& getFoldDrawable() const; - void setFoldDrawable( Drawable* foldDrawable ); + void setFoldDrawable( DrawablePtr foldDrawable ); - Drawable* getFoldedDrawable() const; + const DrawablePtr& getFoldedDrawable() const; - void setFoldedDrawable( Drawable* foldedDrawable ); + void setFoldedDrawable( DrawablePtr foldedDrawable ); bool getFoldsAlwaysVisible() const; @@ -997,10 +997,12 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Text mLineTextCache; size_t mJumpLinesLength{ 5 }; UIIcon* mFileLockIcon{ nullptr }; + DrawablePtr mFileLockDrawable; + int mFileLockDrawableSize{ 0 }; std::string mFileLockIconName{ "file-lock-fill" }; LineWrapType mLineWrapType{ LineWrapType::Viewport }; - Drawable* mFoldDrawable{ nullptr }; - Drawable* mFoldedDrawable{ nullptr }; + DrawablePtr mFoldDrawable; + DrawablePtr mFoldedDrawable; String::HashType mTagFoldRange{ 0 }; Uint32 mTabIndentCharacter{ 187 /*'»'*/ }; CharacterAlignment mTabIndentAlignment{ CharacterAlignment::Center }; @@ -1157,7 +1159,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void updateGlyphWidth(); - Drawable* findIcon( const std::string& name ); + DrawablePtr findIcon( const std::string& name ); void createDefaultContextMenuOptions( UIPopUpMenu* menu ); diff --git a/include/eepp/ui/uiconsole.hpp b/include/eepp/ui/uiconsole.hpp index 884052ab4..68b159196 100644 --- a/include/eepp/ui/uiconsole.hpp +++ b/include/eepp/ui/uiconsole.hpp @@ -343,7 +343,7 @@ class EE_API UIConsole : public UIWidget, UIMenuItem* menuAdd( UIPopUpMenu* menu, const String& translateString, const std::string& icon, const std::string& cmd ); - Drawable* findIcon( const std::string& name ); + DrawablePtr findIcon( const std::string& name ); void copySelection(); diff --git a/include/eepp/ui/uiicon.hpp b/include/eepp/ui/uiicon.hpp index f53fcb690..8028086aa 100644 --- a/include/eepp/ui/uiicon.hpp +++ b/include/eepp/ui/uiicon.hpp @@ -20,15 +20,23 @@ class EE_API UIIcon { const std::string& getName() const; - virtual Drawable* getSize( const int& size ) const; + /** Returns the icon source closest to the requested size. + * + * The returned drawable is shared icon data and must not be mutated by consumers. Use + * createDrawable() when a consumer needs its own mutable drawable instance. */ + virtual const DrawablePtr& getSource( const int& size ) const; - virtual void setSize( const int& size, Drawable* drawable ); + /** Creates a private drawable instance for a consumer. This must not be called from a rendering + * loop; retain the returned instance instead. */ + DrawablePtr createDrawable( const int& size ) const; + + virtual void setSource( const int& size, DrawablePtr drawable ); protected: UIIcon( const std::string& name ); std::string mName; - mutable UnorderedMap mSizes; + mutable UnorderedMap mSizes; }; class EE_API UIGlyphIcon : public UIIcon { @@ -37,7 +45,7 @@ class EE_API UIGlyphIcon : public UIIcon { virtual ~UIGlyphIcon(); - virtual Drawable* getSize( const int& size ) const; + virtual const DrawablePtr& getSource( const int& size ) const; protected: UIGlyphIcon( const std::string& name, FontTrueType* font, const Uint32& codePoint ); @@ -53,13 +61,12 @@ class EE_API UISVGIcon : public UIIcon { virtual ~UISVGIcon(); - virtual Drawable* getSize( const int& size ) const; + virtual const DrawablePtr& getSource( const int& size ) const; protected: UISVGIcon( const std::string& name, const std::string& svgXML ); std::string mSVGXml; - mutable UnorderedMap mSVGs; mutable Sizei mOriSize; mutable int mOriChannels{ 0 }; }; diff --git a/include/eepp/ui/uiimage.hpp b/include/eepp/ui/uiimage.hpp index aa926d993..3467cb2d6 100644 --- a/include/eepp/ui/uiimage.hpp +++ b/include/eepp/ui/uiimage.hpp @@ -24,9 +24,9 @@ class EE_API UIImage : public UIWidget { virtual void setAlpha( const Float& alpha ); - Drawable* getDrawable() const; + const DrawablePtr& getDrawable() const; - UIImage* setDrawable( Drawable* drawable, bool ownIt = false ); + UIImage* setDrawable( DrawablePtr drawable ); UIImage* setDrawable( TexturePtr texture ); @@ -55,13 +55,12 @@ class EE_API UIImage : public UIWidget { protected: UIScaleType mScaleType; - Drawable* mDrawable; - TexturePtr mTexture; + DrawablePtr mDrawable; Color mColor; Vector2f mAlignOffset; Vector2f mDestSize; - Uint32 mResourceChangeCb; - bool mDrawableOwner; + DrawableResourceConnection mResourceChangeConnection; + Uint32 mSpriteChangeCb{ 0 }; bool mDeferLoad{ false }; std::shared_ptr> mAsyncImageAlive; Uint64 mRemoteImageLoadId{ 0 }; @@ -82,9 +81,9 @@ class EE_API UIImage : public UIWidget { void autoAlign(); - void safeDeleteDrawable(); + void clearDrawable(); - void onDrawableResourceEvent( DrawableResource::Event event, DrawableResource* ); + void onDrawableResourceChange(); bool loadFileDrawable( const Network::URI& uri ); diff --git a/include/eepp/ui/uimenu.hpp b/include/eepp/ui/uimenu.hpp index c6b04ee45..923fe7c49 100644 --- a/include/eepp/ui/uimenu.hpp +++ b/include/eepp/ui/uimenu.hpp @@ -24,7 +24,7 @@ class EE_API UIMenu : public UIWidget { virtual bool isType( const Uint32& type ) const; - UIMenuItem* add( const String& text, Drawable* icon = NULL, const String& shortcutText = "" ); + UIMenuItem* add( const String& text, DrawablePtr icon = {}, const String& shortcutText = "" ); UIWidget* add( UIWidget* widget ); @@ -35,7 +35,8 @@ class EE_API UIMenu : public UIWidget { UIMenuRadioButton* addRadioButton( const String& text, const bool& active = false ); - UIMenuSubMenu* addSubMenu( const String& text, Drawable* icon = NULL, UIMenu* subMenu = NULL ); + UIMenuSubMenu* addSubMenu( const String& text, DrawablePtr icon = {}, + UIMenu* subMenu = NULL ); UIWidget* getItem( const Uint32& index ); @@ -55,7 +56,7 @@ class EE_API UIMenu : public UIWidget { void removeAll(); - void insert( const String& text, Drawable* icon, const Uint32& index ); + void insert( const String& text, DrawablePtr icon, const Uint32& index ); void insert( UIWidget* widget, const Uint32& index ); @@ -121,7 +122,7 @@ class EE_API UIMenu : public UIWidget { void resizeMe(); - UIMenuItem* createMenuItem( const String& text, Drawable* icon, + UIMenuItem* createMenuItem( const String& text, DrawablePtr icon, const String& shortcutText = "" ); UIMenuCheckBox* createMenuCheckBox( const String& text, const bool& active, @@ -129,7 +130,7 @@ class EE_API UIMenu : public UIWidget { UIMenuRadioButton* createMenuRadioButton( const String& text, const bool& active ); - UIMenuSubMenu* createSubMenu( const String& text, Drawable* icon, UIMenu* subMenu ); + UIMenuSubMenu* createSubMenu( const String& text, DrawablePtr icon, UIMenu* subMenu ); void onThemeLoaded(); diff --git a/include/eepp/ui/uinode.hpp b/include/eepp/ui/uinode.hpp index f78228dd3..d24270fbd 100644 --- a/include/eepp/ui/uinode.hpp +++ b/include/eepp/ui/uinode.hpp @@ -336,12 +336,11 @@ class EE_API UINode : public Node { * * Enables background fill and sets the specified drawable at the given index. * - * @param drawable Pointer to the Drawable to use. - * @param ownIt If true, the node takes ownership of the drawable. + * @param drawable Drawable instance to use. * @param index The layer index (0-based). * @return Pointer to this node for method chaining. */ - UINode* setBackgroundDrawable( Drawable* drawable, bool ownIt = false, int index = 0 ); + UINode* setBackgroundDrawable( DrawablePtr drawable, int index = 0 ); /** * @brief Sets a background drawable from a skin name. @@ -521,12 +520,11 @@ class EE_API UINode : public Node { * * Enables foreground fill and sets the specified drawable at the given index. * - * @param drawable Pointer to the Drawable to use. - * @param ownIt If true, the node takes ownership of the drawable. + * @param drawable Drawable instance to use. * @param index The layer index (0-based). * @return Pointer to this node for method chaining. */ - UINode* setForegroundDrawable( Drawable* drawable, bool ownIt = false, int index = 0 ); + UINode* setForegroundDrawable( DrawablePtr drawable, int index = 0 ); /** * @brief Sets a foreground drawable from a skin name. diff --git a/include/eepp/ui/uinodedrawable.hpp b/include/eepp/ui/uinodedrawable.hpp index f129580ff..64d1b8744 100644 --- a/include/eepp/ui/uinodedrawable.hpp +++ b/include/eepp/ui/uinodedrawable.hpp @@ -56,11 +56,11 @@ class EE_API UINodeDrawable : public Drawable { virtual void setSize( const Sizef& size ); - Drawable* getDrawable() const; + const DrawablePtr& getDrawable() const; const std::string& getDrawableRef() const; - void setDrawable( Drawable* drawable, const bool& ownIt ); + void setDrawable( DrawablePtr drawable ); void setDrawable( TexturePtr texture ); @@ -131,12 +131,10 @@ class EE_API UINodeDrawable : public Drawable { std::string mPositionY; std::string mSizeEq; bool mNeedsUpdate{ false }; - bool mOwnsDrawable{ false }; bool mColorWasSet{ false }; - Drawable* mDrawable; - TexturePtr mTexture; + DrawablePtr mDrawable; std::string mDrawableRef; - Uint32 mResourceChangeCbId; + DrawableResourceConnection mResourceChangeConnection; RepeatX mRepeatX{ RepeatX::NoRepeat }; RepeatY mRepeatY{ RepeatY::NoRepeat }; std::string mOriginEq{ "padding-box" }; @@ -154,7 +152,7 @@ class EE_API UINodeDrawable : public Drawable { void update(); - Drawable* createDrawable( const std::string& value, const Sizef& size, bool& ownIt ); + DrawablePtr createDrawable( const std::string& value, const Sizef& size ); bool loadRemoteDrawable( const std::string& value ); }; @@ -191,7 +189,7 @@ class EE_API UINodeDrawable : public Drawable { LayerDrawable* getLayer( int index ); - void setDrawable( int index, Drawable* drawable, bool ownIt ); + void setDrawable( int index, DrawablePtr drawable ); void setDrawable( int index, const std::string& drawable ); @@ -238,7 +236,8 @@ class EE_API UINodeDrawable : public Drawable { protected: UINode* mOwner; UIBackgroundDrawable mBackgroundColor; - std::map mGroup; + using LayerDrawablePtr = std::unique_ptr>; + std::map mGroup; Sizef mSize; bool mNeedsUpdate{ true }; bool mClipEnabled{ false }; diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index 87e81276d..5367bcb2a 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -40,7 +40,7 @@ class EE_API UIPushButton : public UIWidget { virtual void setTheme( UITheme* Theme ); - virtual UIPushButton* setIcon( Drawable* icon, bool ownIt = false ); + virtual UIPushButton* setIcon( DrawablePtr icon ); virtual UIImage* getIcon(); diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index 26d73a761..de70bd2b0 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -564,7 +564,7 @@ class EE_API UISceneNode : public SceneNode { * @param drawableSize The desired size of the drawable in pixels. * @return Pointer to the Drawable, or nullptr if not found. */ - Drawable* findIconDrawable( const std::string& iconName, const size_t& drawableSize ); + DrawablePtr findIconDrawable( const std::string& iconName, const size_t& drawableSize ); /** * @brief Gets the keybindings manager. diff --git a/include/eepp/ui/uiskin.hpp b/include/eepp/ui/uiskin.hpp index 79846d2e9..8b10d2844 100644 --- a/include/eepp/ui/uiskin.hpp +++ b/include/eepp/ui/uiskin.hpp @@ -20,9 +20,11 @@ class EE_API UISkin : public StateListDrawable { virtual Sizef getPixelsSize(); - virtual UISkin* clone(); + DrawablePtr createInstance() const; - virtual UISkin* clone( const std::string& NewName ); + ResourcePtr clone() const; + + ResourcePtr clone( const std::string& newName ) const; virtual Rectf getBorderSize( const Uint32& state ); diff --git a/include/eepp/ui/uiskinstate.hpp b/include/eepp/ui/uiskinstate.hpp index 057dc1114..84b65ae88 100644 --- a/include/eepp/ui/uiskinstate.hpp +++ b/include/eepp/ui/uiskinstate.hpp @@ -2,6 +2,7 @@ #define EE_UI_UISKINSTATE_HPP #include +#include #include namespace EE { namespace UI { @@ -10,7 +11,7 @@ class UISkin; class EE_API UISkinState : public UIState { public: - static UISkinState* New( UISkin* skin ); + static UISkinState* New( ResourcePtr skin ); virtual ~UISkinState(); @@ -28,11 +29,11 @@ class EE_API UISkinState : public UIState { bool hasStateColor( const Uint32& state ) const; protected: - UISkin* mSkin; + ResourcePtr mSkin; std::map mColors; Color mCurrentColor; - explicit UISkinState( UISkin* Skin ); + explicit UISkinState( ResourcePtr skin ); void updateState(); diff --git a/include/eepp/ui/uisprite.hpp b/include/eepp/ui/uisprite.hpp index f76da0520..24736150e 100644 --- a/include/eepp/ui/uisprite.hpp +++ b/include/eepp/ui/uisprite.hpp @@ -1,13 +1,9 @@ #ifndef EE_UICUISPRITE_HPP #define EE_UICUISPRITE_HPP +#include #include -namespace EE { namespace Graphics { -class Sprite; -class TextureRegion; -}} // namespace EE::Graphics - namespace EE { namespace UI { class EE_API UISprite : public UIWidget { @@ -26,11 +22,9 @@ class EE_API UISprite : public UIWidget { virtual void setAlpha( const Float& alpha ); - Graphics::Sprite* getSprite() const; + const Graphics::SpritePtr& getSprite() const; - Drawable* getDrawable() const; - - UISprite* setSprite( Graphics::Sprite* sprite ); + UISprite* setSprite( Graphics::SpritePtr sprite ); Color getColor() const; @@ -42,10 +36,6 @@ class EE_API UISprite : public UIWidget { const Vector2f& getAlignOffset() const; - UISprite* setIsSpriteOwner( const bool& dealloc ); - - bool getDeallocSprite(); - virtual bool applyProperty( const StyleSheetProperty& attribute ); virtual std::string getPropertyString( const PropertyDefinition* propertyDef, @@ -54,11 +44,10 @@ class EE_API UISprite : public UIWidget { virtual std::vector getPropertiesImplemented() const; protected: - Graphics::Sprite* mSprite; + Graphics::SpritePtr mSprite; RenderMode mRender; Vector2f mAlignOffset; TextureRegion* mTextureRegionLast; - bool mDealloc; UISprite(); @@ -70,7 +59,6 @@ class EE_API UISprite : public UIWidget { virtual void onSizeChange(); - Uint32 deallocSprite(); }; }} // namespace EE::UI diff --git a/include/eepp/ui/uitabwidget.hpp b/include/eepp/ui/uitabwidget.hpp index 48ccd3012..aa9632bc4 100644 --- a/include/eepp/ui/uitabwidget.hpp +++ b/include/eepp/ui/uitabwidget.hpp @@ -68,7 +68,7 @@ class EE_API UITabWidget : public UIWidget { virtual bool isType( const Uint32& type ) const; - UITab* add( const String& text, UINode* nodeOwned, Drawable* icon = NULL ); + UITab* add( const String& text, UINode* nodeOwned, DrawablePtr icon = {} ); UITabWidget* add( UITab* tab ); @@ -92,7 +92,7 @@ class EE_API UITabWidget : public UIWidget { void removeAllTabs( bool destroyOwnedNode = true, bool immediateClose = false ); - void insertTab( const String& text, UINode* nodeOwned, Drawable* icon, const Uint32& index ); + void insertTab( const String& text, UINode* nodeOwned, DrawablePtr icon, const Uint32& index ); void insertTab( UITab* tab, const Uint32& index ); @@ -265,7 +265,7 @@ class EE_API UITabWidget : public UIWidget { void onThemeLoaded(); - UITab* createTab( const String& text, UINode* nodeOwned, Drawable* icon ); + UITab* createTab( const String& text, UINode* nodeOwned, DrawablePtr icon ); void removeTab( const Uint32& index, bool destroyOwnedNode, bool destroyTab, bool immediateClose, diff --git a/include/eepp/ui/uitextinput.hpp b/include/eepp/ui/uitextinput.hpp index 4fa7c5e69..c2d4ada97 100644 --- a/include/eepp/ui/uitextinput.hpp +++ b/include/eepp/ui/uitextinput.hpp @@ -268,7 +268,7 @@ class EE_API UITextInput : public UITextView, public TextDocument::Client { UIMenuItem* menuAdd( UIPopUpMenu* menu, const String& translateString, const std::string& icon, const std::string& cmd ); - Drawable* findIcon( const std::string& name ); + DrawablePtr findIcon( const std::string& name ); }; }} // namespace EE::UI diff --git a/include/eepp/ui/uitextureregion.hpp b/include/eepp/ui/uitextureregion.hpp index 067fef7be..edc31c1da 100644 --- a/include/eepp/ui/uitextureregion.hpp +++ b/include/eepp/ui/uitextureregion.hpp @@ -51,7 +51,7 @@ class EE_API UITextureRegion : public UIWidget { virtual std::vector getPropertiesImplemented() const; protected: - UIScaleType mScaleType; + UIScaleType mScaleType{ UIScaleType::None }; Graphics::TextureRegion* mTextureRegion; Color mColor; RenderMode mRender; @@ -65,7 +65,9 @@ class EE_API UITextureRegion : public UIWidget { void autoAlign(); - void drawTextureRegion(); + void drawTextureRegion( const Sizef& destSize, const Vector2i& offset ); + + void autoAlign( const Sizef& drawableSize ); }; }} // namespace EE::UI diff --git a/src/eepp/graphics/arcdrawable.cpp b/src/eepp/graphics/arcdrawable.cpp index 5cbf6ace7..8577151ab 100644 --- a/src/eepp/graphics/arcdrawable.cpp +++ b/src/eepp/graphics/arcdrawable.cpp @@ -31,6 +31,18 @@ ArcDrawable::ArcDrawable( const Float& radius, Uint32 segmentsCount, const Float mSegmentsCount = mSegmentsCount > 360 ? 360 : mSegmentsCount; } +DrawablePtr ArcDrawable::createInstance() const { + auto instance = makeResource( mRadius, mSegmentsCount, mArcAngle, mArcStartAngle ); + instance->mOffset = mOffset; + instance->mFillMode = mFillMode; + instance->mBlendMode = mBlendMode; + instance->mLineWidth = mLineWidth; + instance->mSmooth = mSmooth; + instance->mColor = mColor; + instance->mPosition = mPosition; + return instance; +} + Sizef ArcDrawable::getSize() { return Sizef( mRadius * 2, mRadius * 2 ); } diff --git a/src/eepp/graphics/circledrawable.cpp b/src/eepp/graphics/circledrawable.cpp index f2d91b336..78033d7eb 100644 --- a/src/eepp/graphics/circledrawable.cpp +++ b/src/eepp/graphics/circledrawable.cpp @@ -15,4 +15,18 @@ CircleDrawable::CircleDrawable() : ArcDrawable( 0, 64 ) {} CircleDrawable::CircleDrawable( const Float& radius, const Uint32& segmentsCount ) : ArcDrawable( radius, segmentsCount ) {} +DrawablePtr CircleDrawable::createInstance() const { + auto instance = makeResource( mRadius, mSegmentsCount ); + instance->mArcAngle = mArcAngle; + instance->mArcStartAngle = mArcStartAngle; + instance->mOffset = mOffset; + instance->mFillMode = mFillMode; + instance->mBlendMode = mBlendMode; + instance->mLineWidth = mLineWidth; + instance->mSmooth = mSmooth; + instance->mColor = mColor; + instance->mPosition = mPosition; + return instance; +} + }} // namespace EE::Graphics diff --git a/src/eepp/graphics/convexshapedrawable.cpp b/src/eepp/graphics/convexshapedrawable.cpp index a6ee38dd7..5bd602ab7 100644 --- a/src/eepp/graphics/convexshapedrawable.cpp +++ b/src/eepp/graphics/convexshapedrawable.cpp @@ -9,6 +9,19 @@ ConvexShapeDrawable* ConvexShapeDrawable::New() { ConvexShapeDrawable::ConvexShapeDrawable() : PrimitiveDrawable( Drawable::CONVEXSHAPE ) {} +DrawablePtr ConvexShapeDrawable::createInstance() const { + auto instance = makeResource(); + instance->mPolygon = mPolygon; + instance->mIndexColor = mIndexColor; + instance->mFillMode = mFillMode; + instance->mBlendMode = mBlendMode; + instance->mLineWidth = mLineWidth; + instance->mSmooth = mSmooth; + instance->mColor = mColor; + instance->mPosition = mPosition; + return instance; +} + Sizef ConvexShapeDrawable::getSize() { return mPolygon.getBounds().getSize(); } diff --git a/src/eepp/graphics/drawable.cpp b/src/eepp/graphics/drawable.cpp index 5bce0691b..5f1ae516f 100644 --- a/src/eepp/graphics/drawable.cpp +++ b/src/eepp/graphics/drawable.cpp @@ -9,6 +9,10 @@ Drawable::Drawable( Type drawableType ) : Drawable::~Drawable() {} +DrawablePtr Drawable::createInstance() const { + return {}; +} + void Drawable::setAlpha( Uint8 alpha ) { if ( mColor.a != alpha ) { mColor.a = alpha; diff --git a/src/eepp/graphics/drawablegroup.cpp b/src/eepp/graphics/drawablegroup.cpp index 5148436f0..6e0bf1e1b 100644 --- a/src/eepp/graphics/drawablegroup.cpp +++ b/src/eepp/graphics/drawablegroup.cpp @@ -4,36 +4,47 @@ namespace EE { namespace Graphics { -DrawableGroup* DrawableGroup::New() { - return eeNew( DrawableGroup, () ); +ResourcePtr DrawableGroup::New() { + return makeResource(); } DrawableGroup::DrawableGroup() : - Drawable( Drawable::GROUP ), - mNeedsUpdate( true ), - mClipEnabled( false ), - mDrawableOwner( true ) {} + Drawable( Drawable::GROUP ), mNeedsUpdate( true ), mClipEnabled( false ) {} DrawableGroup::~DrawableGroup() { clearDrawables(); } -void DrawableGroup::clearDrawables() { - if ( mDrawableOwner ) { - for ( std::size_t i = 0; i < mGroup.size(); i++ ) { - Drawable* drawable = mGroup[i]; - eeSAFE_DELETE( drawable ); - } +DrawablePtr DrawableGroup::createInstance() const { + auto instance = makeResource(); + instance->mPosition = mPosition; + instance->mColor = mColor; + instance->mSize = mSize; + instance->mClipEnabled = mClipEnabled; + + for ( const auto& drawable : mGroup ) { + if ( !drawable ) + continue; + DrawablePtr drawableInstance = drawable->createInstance(); + if ( !drawableInstance ) + return {}; + instance->addDrawable( std::move( drawableInstance ) ); } + return instance; +} + +void DrawableGroup::clearDrawables() { mGroup.clear(); mPos.clear(); } -Drawable* DrawableGroup::addDrawable( Drawable* drawable ) { - mGroup.push_back( drawable ); +DrawablePtr DrawableGroup::addDrawable( DrawablePtr drawable ) { + if ( !drawable ) + return {}; mPos.push_back( drawable->getPosition() ); - return drawable; + mGroup.push_back( std::move( drawable ) ); + return mGroup.back(); } Uint32 DrawableGroup::getDrawableCount() const { @@ -48,15 +59,7 @@ void DrawableGroup::setClipEnabled( bool clipEnabled ) { mClipEnabled = clipEnabled; } -bool DrawableGroup::isDrawableOwner() const { - return mDrawableOwner; -} - -void DrawableGroup::setDrawableOwner( bool drawableOwner ) { - mDrawableOwner = drawableOwner; -} - -std::vector& DrawableGroup::getGroup() { +std::vector& DrawableGroup::getGroup() { return mGroup; } @@ -89,7 +92,7 @@ void DrawableGroup::draw( const Vector2f& position, const Sizef& size ) { GLi->getClippingMask()->clipPlaneEnable( mPosition.x, mPosition.y, mSize.x, mSize.y ); for ( std::size_t i = 0; i < mGroup.size(); i++ ) { - Drawable* drawable = mGroup[i]; + Drawable* drawable = mGroup[i].get(); drawable->draw(); } @@ -111,7 +114,7 @@ void DrawableGroup::onPositionChange() { void DrawableGroup::onAlphaChange() { for ( std::size_t i = 0; i < mGroup.size(); i++ ) { - Drawable* drawable = mGroup[i]; + Drawable* drawable = mGroup[i].get(); drawable->setAlpha( getAlpha() ); } } @@ -120,7 +123,7 @@ void DrawableGroup::update() { Sizef nSize( mSize ); for ( std::size_t i = 0; i < mGroup.size(); i++ ) { - Drawable* drawable = mGroup[i]; + Drawable* drawable = mGroup[i].get(); Vector2f pos( mPosition + mPos[i] ); Sizef s( mPos[i] + drawable->getSize() ); diff --git a/src/eepp/graphics/drawableresource.cpp b/src/eepp/graphics/drawableresource.cpp index cff407930..6d433a12d 100644 --- a/src/eepp/graphics/drawableresource.cpp +++ b/src/eepp/graphics/drawableresource.cpp @@ -2,19 +2,59 @@ namespace EE { namespace Graphics { +DrawableResourceConnection::DrawableResourceConnection( + std::weak_ptr state, Uint32 id ) : + mState( std::move( state ) ), mId( id ) {} + +DrawableResourceConnection::~DrawableResourceConnection() { + disconnect(); +} + +DrawableResourceConnection::DrawableResourceConnection( + DrawableResourceConnection&& other ) noexcept : + mState( std::move( other.mState ) ), mId( other.mId ) { + other.mId = 0; +} + +DrawableResourceConnection& +DrawableResourceConnection::operator=( DrawableResourceConnection&& other ) noexcept { + if ( this != &other ) { + disconnect(); + mState = std::move( other.mState ); + mId = other.mId; + other.mId = 0; + } + return *this; +} + +void DrawableResourceConnection::disconnect() { + if ( mId != 0 ) { + if ( auto state = mState.lock() ) + state->callbacks.erase( mId ); + } + mState.reset(); + mId = 0; +} + +DrawableResourceConnection::operator bool() const { + return mId != 0 && !mState.expired(); +} + DrawableResource::DrawableResource( Type drawableType ) : - Drawable( drawableType ), mId( 0 ), mNumCallBacks( 0 ) { + Drawable( drawableType ), + mId( 0 ), + mCallbackState( std::make_shared() ) { createUnnamed(); } DrawableResource::DrawableResource( Type drawableType, const std::string& name ) : - Drawable( drawableType ), mId( 0 ), mNumCallBacks( 0 ) { + Drawable( drawableType ), + mId( 0 ), + mCallbackState( std::make_shared() ) { setName( name ); } -DrawableResource::~DrawableResource() { - sendEvent( Event::Unload ); -} +DrawableResource::~DrawableResource() {} const String::HashType& DrawableResource::getId() const { return mId; @@ -39,23 +79,23 @@ bool DrawableResource::isDrawableResource() const { } void DrawableResource::onResourceChange() { - sendEvent( Event::Change ); + sendResourceChanged(); } -void DrawableResource::sendEvent( const Event& event ) { - for ( const auto& cb : mCallbacks ) { - cb.second( cb.first, event, this ); - } +void DrawableResource::sendResourceChanged() { + std::vector callbacks; + callbacks.reserve( mCallbackState->callbacks.size() ); + for ( const auto& callback : mCallbackState->callbacks ) + callbacks.emplace_back( callback.second ); + for ( const auto& callback : callbacks ) + callback( *this ); } -Uint32 DrawableResource::pushResourceChangeCallback( const OnResourceChangeCallback& cb ) { - mNumCallBacks++; - mCallbacks[mNumCallBacks] = cb; - return mNumCallBacks; -} - -bool DrawableResource::popResourceChangeCallback( const Uint32& callbackId ) { - return mCallbacks.erase( callbackId ) > 0; +DrawableResourceConnection +DrawableResource::connectResourceChange( OnResourceChangeCallback callback ) { + Uint32 id = ++mCallbackState->nextId; + mCallbackState->callbacks.emplace( id, std::move( callback ) ); + return DrawableResourceConnection( mCallbackState, id ); } }} // namespace EE::Graphics diff --git a/src/eepp/graphics/drawablesearcher.cpp b/src/eepp/graphics/drawablesearcher.cpp index c707bd42f..74986327a 100644 --- a/src/eepp/graphics/drawablesearcher.cpp +++ b/src/eepp/graphics/drawablesearcher.cpp @@ -19,42 +19,42 @@ namespace EE { namespace Graphics { bool DrawableSearcher::sPrintWarnings = false; -static Drawable* getSprite( const std::string& sprite ) { +static DrawablePtr getSprite( const std::string& sprite ) { std::vector tTextureRegionVec = TextureAtlasManager::instance()->getTextureRegionsByPattern( sprite ); if ( tTextureRegionVec.size() ) { - Sprite* tSprite = Graphics::Sprite::New(); + SpritePtr tSprite = Sprite::New(); tSprite->createAnimation(); tSprite->addFrames( tTextureRegionVec ); return tSprite; } - return NULL; + return {}; } -static Drawable* searchByNameInternal( const std::string& name, ResourceScope& resourceScope ) { +static DrawablePtr searchByNameInternal( const std::string& name, ResourceScope& resourceScope ) { String::HashType id = String::hash( name ); - Drawable* drawable = TextureAtlasManager::instance()->getTextureRegionById( id ); + Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); - if ( NULL == drawable ) { - drawable = NinePatchManager::instance()->getById( id ); + if ( NULL == source ) { + source = NinePatchManager::instance()->getById( id ); } - if ( NULL == drawable ) { - drawable = resourceScope.findTexture( name ).get(); + if ( source ) { + return source->createInstance(); } - return drawable; + TexturePtr texture = resourceScope.findTexture( name ); + return texture ? texture->createInstance() : DrawablePtr{}; } -static Drawable* parseDataURI( const std::string& name, ResourceScope& scope ) { +static DrawablePtr parseDataURI( const std::string& name, ResourceScope& scope ) { auto hash = MD5::fromString( name ).toHexString(); TexturePtr texture = scope.findTexture( hash ); - Drawable* drawable = texture.get(); std::string::size_type formatAndEncSep; - if ( nullptr == drawable && + if ( !texture && ( formatAndEncSep = name.find_first_of( ',' ) ) != std::string::npos ) { std::string decodingType = "urldecode"; std::string mediaType = name.substr( 0, formatAndEncSep ); @@ -103,16 +103,16 @@ static Drawable* parseDataURI( const std::string& name, ResourceScope& scope ) { if ( tex ) { tex->setName( hash ); scope.publishLocal( hash, tex ); - drawable = tex.get(); + texture = std::move( tex ); } } - return drawable; + return texture ? texture->createInstance() : DrawablePtr{}; } -Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSearchSprite, +DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstSearchSprite, Network::URI referer, ResourceScope* requestedResourceScope ) { - Drawable* drawable = NULL; + DrawablePtr drawable; if ( name.size() ) { ResourceScope& resourceScope = @@ -126,7 +126,7 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea drawable = getSprite( name ); } - if ( NULL != drawable ) { + if ( drawable ) { return drawable; } @@ -135,18 +135,22 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea if ( name[0] == '@' ) { if ( String::startsWith( name, "@textureregion/" ) ) { - drawable = - TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ); + if ( Drawable* source = + TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ) ) + drawable = source->createInstance(); } else if ( String::startsWith( name, "@image/" ) ) { - drawable = resourceScope.findTexture( name.substr( 7 ) ).get(); + TexturePtr texture = resourceScope.findTexture( name.substr( 7 ) ); + drawable = texture ? texture->createInstance() : DrawablePtr{}; } else if ( String::startsWith( name, "@texture/" ) ) { - drawable = resourceScope.findTexture( name.substr( 9 ) ).get(); + TexturePtr texture = resourceScope.findTexture( name.substr( 9 ) ); + drawable = texture ? texture->createInstance() : DrawablePtr{}; } else if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) { drawable = getSprite( name.substr( 8 ) ); } else if ( String::startsWith( name, "@drawable/" ) ) { drawable = searchByNameInternal( name.substr( 10 ), resourceScope ); } else if ( String::startsWith( name, "@9p/" ) ) { - drawable = NinePatchManager::instance()->getByName( name.substr( 4 ) ); + if ( Drawable* source = NinePatchManager::instance()->getByName( name.substr( 4 ) ) ) + drawable = source->createInstance(); } else { drawable = searchByNameInternal( name, resourceScope ); } @@ -162,16 +166,17 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea FileSystem::filePathRemoveProcessPath( filePath ); - drawable = resourceScope.findTexture( filePath ).get(); + TexturePtr texture = resourceScope.findTexture( filePath ); - if ( NULL == drawable ) { + if ( !texture ) { TexturePtr tex = TextureFactory::instance()->loadFromFile( filePath ); if ( tex ) { resourceScope.publishLocal( filePath, tex ); - drawable = tex.get(); + texture = std::move( tex ); } } + drawable = texture ? texture->createInstance() : DrawablePtr{}; } else if ( String::startsWith( name, "http://" ) || String::startsWith( name, "https://" ) ) { TexturePtr texture = resourceScope.findTexture( name ); @@ -204,7 +209,7 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea URI( name ), Seconds( 5 ), {}, headers ); } - drawable = texture.get(); + drawable = texture ? texture->createInstance() : DrawablePtr{}; } else if ( String::startsWith( name, "data:image/" ) ) { drawable = parseDataURI( name, resourceScope ); } else { @@ -212,16 +217,17 @@ Drawable* DrawableSearcher::searchByName( const std::string& name, bool firstSea } } - if ( NULL == drawable && sPrintWarnings ) + if ( !drawable && sPrintWarnings ) Log::warning( "DrawableSearcher::searchByName: \"%s\" not found", name.c_str() ); return drawable; } -Drawable* DrawableSearcher::searchById( const Uint32& id ) { - Drawable* drawable = TextureAtlasManager::instance()->getTextureRegionById( id ); +DrawablePtr DrawableSearcher::searchById( const Uint32& id ) { + Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); + DrawablePtr drawable = source ? source->createInstance() : DrawablePtr{}; - if ( NULL == drawable && sPrintWarnings ) + if ( !drawable && sPrintWarnings ) Log::warning( "DrawableSearcher::searchById: \"%ld\" not found", id ); return drawable; diff --git a/src/eepp/graphics/glyphdrawable.cpp b/src/eepp/graphics/glyphdrawable.cpp index f1d189749..0d26c68f5 100644 --- a/src/eepp/graphics/glyphdrawable.cpp +++ b/src/eepp/graphics/glyphdrawable.cpp @@ -80,6 +80,18 @@ bool GlyphDrawable::isStateful() { return false; } +DrawablePtr GlyphDrawable::createInstance() const { + auto instance = makeResource( mTexture, mSrcRect.asInt(), mDestSize, mName ); + instance->setPixelDensity( mPixelDensity ); + instance->setGlyphOffset( mGlyphOffset ); + instance->setDrawMode( mDrawMode ); + instance->setIsItalic( mIsItalic ); + instance->setAdvance( mAdvance ); + instance->setColor( mColor ); + instance->setPosition( mPosition ); + return instance; +} + const TexturePtr& GlyphDrawable::getTexture() const { return mTexture; } diff --git a/src/eepp/graphics/ninepatch.cpp b/src/eepp/graphics/ninepatch.cpp index 6b1e511b4..05c3d5e63 100644 --- a/src/eepp/graphics/ninepatch.cpp +++ b/src/eepp/graphics/ninepatch.cpp @@ -25,9 +25,6 @@ NinePatch::NinePatch( TexturePtr tex, int left, int top, int right, int bottom, DrawableResource( Drawable::NINEPATCH, name ), mRect( left, top, right, bottom ), mPixelDensity( pixelDensity ) { - for ( Int32 i = 0; i < SideCount; i++ ) - mDrawable[i] = NULL; - if ( NULL != tex ) { mSize = tex->getPixelsSize(); @@ -40,9 +37,6 @@ NinePatch::NinePatch( TextureRegion* textureRegion, int left, int top, int right DrawableResource( Drawable::NINEPATCH, name ), mRect( left, top, right, bottom ), mPixelDensity( 1 ) { - for ( Int32 i = 0; i < SideCount; i++ ) - mDrawable[i] = NULL; - if ( NULL != textureRegion && textureRegion->getTexture() != NULL ) { mPixelDensity = textureRegion->getPixelDensity(); @@ -53,7 +47,7 @@ NinePatch::NinePatch( TextureRegion* textureRegion, int left, int top, int right createFromTexture( textureRegion->getTexture(), left, top, right, bottom ); for ( int i = 0; i < SideCount; i++ ) { - TextureRegion* side = static_cast( mDrawable[i] ); + TextureRegion* side = mDrawable[i].get(); side->setPixelDensity( textureRegion->getPixelDensity() ); @@ -69,9 +63,27 @@ NinePatch::NinePatch( TextureRegion* textureRegion, int left, int top, int right } } -NinePatch::~NinePatch() { - for ( Int32 i = 0; i < SideCount; i++ ) - eeSAFE_DELETE( mDrawable[i] ); +NinePatch::~NinePatch() {} + +DrawablePtr NinePatch::createInstance() const { + if ( !mDrawable[Center] ) + return {}; + auto instance = makeResource( mDrawable[Center]->getTexture(), mRect.Left, mRect.Top, + mRect.Right, mRect.Bottom, mPixelDensity, mName ); + for ( int i = 0; i < SideCount; ++i ) { + instance->mDrawable[i] = + mDrawable[i] ? std::static_pointer_cast( mDrawable[i]->createInstance() ) + : TextureRegionPtr{}; + if ( mDrawable[i] && !instance->mDrawable[i] ) + return {}; + } + instance->mRect = mRect; + instance->mRectf = mRectf; + instance->mSize = mSize; + instance->mDestSize = mDestSize; + instance->setColor( mColor ); + instance->setPosition( mPosition ); + return instance; } Sizef NinePatch::getSize() { @@ -115,7 +127,7 @@ void NinePatch::draw( const Vector2f& position, const Sizef& size ) { TextureRegion* NinePatch::getTextureRegion( const int& side ) { if ( side < SideCount ) - return mDrawable[side]; + return mDrawable[side].get(); return NULL; } @@ -123,24 +135,24 @@ void NinePatch::createFromTexture( const TexturePtr& tex, int left, int top, int int bottom ) { Rect r; r = Rect( 0, top, left, mSize.getHeight() - bottom ); - mDrawable[Left] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[Left] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( mSize.getWidth() - right, top, mSize.getWidth(), mSize.getHeight() - bottom ); - mDrawable[Right] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[Right] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( left, mSize.getHeight() - bottom, mSize.getWidth() - right, mSize.getHeight() ); - mDrawable[Down] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[Down] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( left, 0, mSize.getWidth() - right, top ); - mDrawable[Up] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[Up] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( 0, 0, left, top ); - mDrawable[UpLeft] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[UpLeft] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( mSize.getWidth() - right, 0, mSize.getWidth(), top ); - mDrawable[UpRight] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[UpRight] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( 0, mSize.getHeight() - bottom, left, mSize.getHeight() ); - mDrawable[DownLeft] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[DownLeft] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( mSize.getWidth() - right, mSize.getHeight() - bottom, mSize.getWidth(), mSize.getHeight() ); - mDrawable[DownRight] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[DownRight] = makeResource( tex, r, r.getSize().asFloat() ); r = Rect( left, top, mSize.getWidth() - right, mSize.getHeight() - bottom ); - mDrawable[Center] = TextureRegion::New( tex, r, r.getSize().asFloat() ); + mDrawable[Center] = makeResource( tex, r, r.getSize().asFloat() ); mRect = Rect( left, top, right, bottom ); diff --git a/src/eepp/graphics/rectangledrawable.cpp b/src/eepp/graphics/rectangledrawable.cpp index 8a2e0e5f9..fc48542e7 100644 --- a/src/eepp/graphics/rectangledrawable.cpp +++ b/src/eepp/graphics/rectangledrawable.cpp @@ -29,6 +29,21 @@ RectangleDrawable::RectangleDrawable( const Vector2f& position, const Sizef& siz mPosition = position; } +DrawablePtr RectangleDrawable::createInstance() const { + auto instance = makeResource( mPosition, mSize ); + instance->mRotation = mRotation; + instance->mScale = mScale; + instance->mCorners = mCorners; + instance->mRectColors = mRectColors; + instance->mUsingRectColors = mUsingRectColors; + instance->mFillMode = mFillMode; + instance->mBlendMode = mBlendMode; + instance->mLineWidth = mLineWidth; + instance->mSmooth = mSmooth; + instance->mColor = mColor; + return instance; +} + Sizef RectangleDrawable::getSize() { return mSize; } diff --git a/src/eepp/graphics/scrollparallax.cpp b/src/eepp/graphics/scrollparallax.cpp index 16ebc652e..fd633b2b3 100644 --- a/src/eepp/graphics/scrollparallax.cpp +++ b/src/eepp/graphics/scrollparallax.cpp @@ -15,11 +15,13 @@ ScrollParallax::ScrollParallax( TextureRegion* textureRegion, const Vector2f& Po } TextureRegion* ScrollParallax::getTextureRegion() const { - return mTextureRegion; + return mTextureRegion.get(); } void ScrollParallax::setTextureRegion( TextureRegion* textureRegion ) { - mTextureRegion = textureRegion; + mTextureRegion = + textureRegion ? std::static_pointer_cast( textureRegion->createInstance() ) + : TextureRegionPtr{}; setTextureRegion(); } @@ -43,7 +45,9 @@ void ScrollParallax::setAABB() { bool ScrollParallax::create( TextureRegion* textureRegion, const Vector2f& Position, const Sizef& Size, const Vector2f& Speed, const Color& Color, const BlendMode& Blend ) { - mTextureRegion = textureRegion; + mTextureRegion = + textureRegion ? std::static_pointer_cast( textureRegion->createInstance() ) + : TextureRegionPtr{}; mPos = Position; mSize = Size; mInitPos = mPos; @@ -97,8 +101,8 @@ void ScrollParallax::draw() { Vector2f Pos = mPos; - Pos.x = ( Float )(Int32)Pos.x; - Pos.y = ( Float )(Int32)Pos.y; + Pos.x = (Float)(Int32)Pos.x; + Pos.y = (Float)(Int32)Pos.y; if ( mSpeed.x > 0.f ) Pos.x -= mRealSize.getWidth(); @@ -111,53 +115,52 @@ void ScrollParallax::draw() { for ( Int32 y = -1; y < mTiles.y; y++ ) { for ( Int32 x = -1; x < mTiles.x; x++ ) { - Rect Rect = mRect; + Rect rect = mRect; Rectf AABB( Pos.x, Pos.y, Pos.x + mRealSize.getWidth(), Pos.y + mRealSize.getHeight() ); if ( AABB.intersect( mAABB ) ) { if ( Pos.x < mAABB.Left ) { - Rect.Left += ( Int32 )( ( mAABB.Left - Pos.x ) * pd ); + rect.Left += (Int32)( ( mAABB.Left - Pos.x ) * pd ); AABB.Left = mAABB.Left; } if ( Pos.x + mRealSize.getWidth() > mAABB.Right ) { - Rect.Right -= - ( Int32 )( ( ( Pos.x + mRealSize.getWidth() ) - mAABB.Right ) * pd ); + rect.Right -= + (Int32)( ( ( Pos.x + mRealSize.getWidth() ) - mAABB.Right ) * pd ); } if ( Pos.y < mAABB.Top ) { - Rect.Top += ( Int32 )( ( mAABB.Top - Pos.y ) * pd ); + rect.Top += (Int32)( ( mAABB.Top - Pos.y ) * pd ); AABB.Top = mAABB.Top; } if ( Pos.y + mRealSize.getHeight() > mAABB.Bottom ) { - Rect.Bottom -= - ( Int32 )( ( ( Pos.y + mRealSize.getHeight() ) - mAABB.Bottom ) * pd ); + rect.Bottom -= + (Int32)( ( ( Pos.y + mRealSize.getHeight() ) - mAABB.Bottom ) * pd ); } - mTextureRegion->setSrcRect( Rect ); - mTextureRegion->setDestSize( - Vector2f( Rect.getSize().x * ps, Rect.getSize().y * ps ) ); - - if ( !( Rect.Right == 0 || Rect.Bottom == 0 ) ) - mTextureRegion->draw( AABB.Left, AABB.Top, mColor, 0.f, Vector2f::One, - mBlend ); + const TexturePtr& texture = mTextureRegion->getTexture(); + if ( texture && !( rect.Right == 0 || rect.Bottom == 0 ) ) { + const Vector2i& offset = mTextureRegion->getOffset(); + texture->drawEx( AABB.Left + offset.x, AABB.Top + offset.y, + rect.getSize().x * ps, rect.getSize().y * ps, 0.f, + Vector2f::One, mColor, mColor, mColor, mColor, mBlend, + RENDER_NORMAL, OriginPoint( OriginPoint::OriginCenter ), + rect ); + } } Pos.x += mRealSize.getWidth(); } - Pos.x = ( Float )(Int32)mPos.x; + Pos.x = (Float)(Int32)mPos.x; if ( mSpeed.x > 0.f ) Pos.x -= mRealSize.getWidth(); Pos.y += mRealSize.getHeight(); } - - mTextureRegion->setSrcRect( mRect ); - mTextureRegion->resetDestSize(); } } diff --git a/src/eepp/graphics/sprite.cpp b/src/eepp/graphics/sprite.cpp index 9517f2ad5..42bc69a60 100644 --- a/src/eepp/graphics/sprite.cpp +++ b/src/eepp/graphics/sprite.cpp @@ -10,38 +10,41 @@ using namespace EE::Window; namespace EE { namespace Graphics { -Sprite* Sprite::New() { - return eeNew( Sprite, () ); +SpritePtr Sprite::New() { + return makeResource(); } -Sprite* Sprite::New( const std::string& name, const std::string& extension, - TextureAtlas* SearchInTextureAtlas ) { - return eeNew( Sprite, ( name, extension, SearchInTextureAtlas ) ); +SpritePtr Sprite::New( const std::string& name, const std::string& extension, + TextureAtlas* SearchInTextureAtlas ) { + return makeResource( name, extension, SearchInTextureAtlas ); } -Sprite* Sprite::New( TextureRegion* TextureRegion ) { - return eeNew( Sprite, ( TextureRegion ) ); +SpritePtr Sprite::New( TextureRegion* TextureRegion ) { + return makeResource( TextureRegion ); } -Sprite* Sprite::New( ResourceId textureId, const Sizef& DestSize, const Vector2i& offset, - const Rect& TexSector ) { - return eeNew( Sprite, ( textureId, DestSize, offset, TexSector ) ); +SpritePtr Sprite::New( ResourceId textureId, const Sizef& DestSize, const Vector2i& offset, + const Rect& TexSector ) { + return makeResource( textureId, DestSize, offset, TexSector ); } -Sprite* Sprite::fromGif( IOStream& stream ) { +SpritePtr Sprite::fromGif( IOStream& stream ) { auto [gif, delay] = Texture::loadGif( stream ); - Sprite* sprite = Sprite::New(); + SpritePtr sprite = Sprite::New(); for ( const auto& texture : gif ) sprite->addFrame( texture ); sprite->setAnimationSpeed( 1000.f / (float)delay ); - sprite->setAsTextureRegionOwner( true ); return sprite; } Sprite::Sprite() : Drawable( Drawable::SPRITE ) {} +Sprite::Sprite( const Sprite& other ) : Drawable( Drawable::SPRITE ) { + *this = other; +} + Sprite::Sprite( const std::string& name, const std::string& extension, TextureAtlas* SearchInTextureAtlas ) : Drawable( Drawable::SPRITE ) { @@ -59,16 +62,30 @@ Sprite::Sprite( ResourceId textureId, const Sizef& DestSize, const Vector2i& Off } Sprite::~Sprite() { - cleanUpResources(); eeSAFE_DELETE_ARRAY( mVertexColors ); } Sprite& Sprite::operator=( const Sprite& Other ) { + if ( this == &Other ) + return *this; + mDrawableType = Other.mDrawableType; - mFrames = Other.mFrames; + mFrames.clear(); + mFrames.reserve( Other.mFrames.size() ); + for ( const Frame& otherFrame : Other.mFrames ) { + Frame frame; + frame.Spr.reserve( otherFrame.Spr.size() ); + for ( const TextureRegionPtr& region : otherFrame.Spr ) { + frame.Spr.emplace_back( + region ? std::static_pointer_cast( region->createInstance() ) + : nullptr ); + } + mFrames.emplace_back( std::move( frame ) ); + } mFlags = Other.mFlags; mColor = Other.mColor; mPosition = Other.mPosition; + mOrigin = Other.mOrigin; mRotation = Other.mRotation; mScale = Other.mScale; mAnimSpeed = Other.mAnimSpeed; @@ -80,8 +97,10 @@ Sprite& Sprite::operator=( const Sprite& Other ) { mCurrentSubFrame = Other.mCurrentSubFrame; mSubFrames = Other.mSubFrames; mAnimTo = Other.mAnimTo; - mCallbacks = Other.mCallbacks; - mNumCallBacks = Other.mNumCallBacks; + mCallbacks.clear(); + mNumCallBacks = 0; + + eeSAFE_DELETE_ARRAY( mVertexColors ); if ( NULL != Other.mVertexColors ) { mVertexColors = eeNewArray( Color, 4 ); @@ -96,39 +115,12 @@ Sprite& Sprite::operator=( const Sprite& Other ) { return *this; } -Sprite Sprite::clone() { - Sprite Spr; +SpritePtr Sprite::clone() const { + return makeResource( *this ); +} - Spr.mDrawableType = mDrawableType; - Spr.mColor = mColor; - Spr.mFrames = mFrames; - Spr.mFlags = mFlags; - Spr.mPosition = mPosition; - Spr.mRotation = mRotation; - Spr.mScale = mScale; - Spr.mAnimSpeed = mAnimSpeed; - Spr.mRepetitions = mRepetitions; - Spr.mBlend = mBlend; - Spr.mEffect = mEffect; - Spr.mCurrentFrame = mCurrentFrame; - Spr.mfCurrentFrame = mfCurrentFrame; - Spr.mCurrentSubFrame = mCurrentSubFrame; - Spr.mSubFrames = mSubFrames; - Spr.mAnimTo = mAnimTo; - Spr.mCallbacks = mCallbacks; - Spr.mNumCallBacks = mNumCallBacks; - - if ( NULL != mVertexColors ) { - Spr.mVertexColors = eeNewArray( Color, 4 ); - Spr.mVertexColors[0] = mVertexColors[0]; - Spr.mVertexColors[1] = mVertexColors[1]; - Spr.mVertexColors[2] = mVertexColors[2]; - Spr.mVertexColors[3] = mVertexColors[3]; - } else { - Spr.mVertexColors = NULL; - } - - return Spr; +DrawablePtr Sprite::createInstance() const { + return clone(); } void Sprite::clearFrame() { @@ -138,21 +130,7 @@ void Sprite::clearFrame() { mFrames.clear(); } -void Sprite::cleanUpResources() { - if ( isTextureRegionOwner() ) { - size_t frames = getNumFrames(); - - for ( size_t i = 0; i < frames; i++ ) { - for ( size_t f = 0; f < mFrames[i].Spr.size(); f++ ) { - TextureRegion* region = mFrames[i].Spr[f]; - GlobalTextureAtlas::instance()->remove( region ); - } - } - } -} - void Sprite::reset() { - cleanUpResources(); clearFrame(); mFlags = SPRITE_FLAG_AUTO_ANIM | SPRITE_FLAG_EVENTS_ENABLED; @@ -402,6 +380,14 @@ bool Sprite::addFramesByPattern( const std::string& name, const std::string& ext bool Sprite::addSubFrame( TextureRegion* TextureRegion, const unsigned int& NumFrame, const unsigned int& NumSubFrame ) { + return addSubFrame( TextureRegion ? std::static_pointer_cast( + TextureRegion->createInstance() ) + : TextureRegionPtr{}, + NumFrame, NumSubFrame ); +} + +bool Sprite::addSubFrame( TextureRegionPtr TextureRegion, const unsigned int& NumFrame, + const unsigned int& NumSubFrame ) { unsigned int NF, NSF; if ( NumFrame >= mFrames.size() ) @@ -458,7 +444,7 @@ bool Sprite::addSubFrame( TexturePtr tex, const unsigned int& NumFrame, const unsigned int& NumSubFrame, const Sizef& DestSize, const Vector2i& Offset, const Rect& TexSector ) { if ( tex ) { - TextureRegion* region = GlobalTextureAtlas::instance()->add( TextureRegion::New() ); + TextureRegionPtr region = makeResource(); region->setTexture( std::move( tex ) ); if ( TexSector.Right > 0 && TexSector.Bottom > 0 ) @@ -475,7 +461,7 @@ bool Sprite::addSubFrame( TexturePtr tex, const unsigned int& NumFrame, region->setDestSize( destSize ); region->setOffset( Offset ); - return addSubFrame( region, NumFrame, NumSubFrame ); + return addSubFrame( std::move( region ), NumFrame, NumSubFrame ); } return false; } @@ -732,21 +718,21 @@ bool Sprite::getAutoAnimate() const { TextureRegion* Sprite::getCurrentTextureRegion() { if ( mFrames.size() ) - return mFrames[mCurrentFrame].Spr[mCurrentSubFrame]; + return mFrames[mCurrentFrame].Spr[mCurrentSubFrame].get(); return NULL; } TextureRegion* Sprite::getTextureRegion( const unsigned int& frame ) { if ( frame < mFrames.size() ) - return mFrames[frame].Spr[mCurrentSubFrame]; + return mFrames[frame].Spr[mCurrentSubFrame].get(); return NULL; } TextureRegion* Sprite::getTextureRegion( const unsigned int& frame, const unsigned int& SubFrame ) { if ( frame < mFrames.size() ) - return mFrames[frame].Spr[SubFrame]; + return mFrames[frame].Spr[SubFrame].get(); return NULL; } @@ -897,18 +883,6 @@ void Sprite::fireEvent( const Uint32& Event ) { } } -Sprite& Sprite::setAsTextureRegionOwner( bool set ) { - if ( set ) - mFlags |= SPRITE_FLAG_TEXTURE_REGION_OWNER; - else - mFlags &= ~SPRITE_FLAG_TEXTURE_REGION_OWNER; - return *this; -} - -bool Sprite::isTextureRegionOwner() const { - return mFlags & SPRITE_FLAG_TEXTURE_REGION_OWNER; -} - void Sprite::setOrigin( const OriginPoint& origin ) { mOrigin = origin; } diff --git a/src/eepp/graphics/statelistdrawable.cpp b/src/eepp/graphics/statelistdrawable.cpp index 4010bdcc2..18ad874df 100644 --- a/src/eepp/graphics/statelistdrawable.cpp +++ b/src/eepp/graphics/statelistdrawable.cpp @@ -13,27 +13,29 @@ StateListDrawable::StateListDrawable( Type type, const std::string& name ) : StateListDrawable::StateListDrawable( const std::string& name ) : StatefulDrawable( STATELIST, name ), mCurrentState( 0 ), mCurrentDrawable( NULL ) {} -StateListDrawable::~StateListDrawable() { - clearDrawables(); -} +StateListDrawable::~StateListDrawable() {} void StateListDrawable::clearDrawables() { - std::vector removeOwnershipState; + mCurrentDrawable = nullptr; + mDrawables.clear(); +} - for ( auto it = mDrawables.begin(); it != mDrawables.end(); ++it ) { - Drawable* drawable = it->second; - - if ( mDrawablesOwnership[drawable] ) { - removeOwnershipState.push_back( drawable ); - eeSAFE_DELETE( drawable ); +DrawablePtr StateListDrawable::createInstance() const { + auto instance = ResourcePtr( eeNew( StateListDrawable, ( mName ) ), + ResourceDeleter() ); + instance->setColor( mColor ); + instance->setPosition( mPosition ); + for ( const auto& state : mDrawables ) { + if ( state.second ) { + DrawablePtr drawable = state.second->createInstance(); + if ( !drawable ) + return {}; + instance->setStateDrawable( state.first, std::move( drawable ) ); } } - - for ( auto& removeOwnership : removeOwnershipState ) { - mDrawablesOwnership.erase( removeOwnership ); - } - - mDrawables.clear(); + instance->mDrawableColors = mDrawableColors; + instance->setState( mCurrentState ); + return instance; } Sizef StateListDrawable::getSize() { @@ -94,17 +96,11 @@ bool StateListDrawable::isStateful() { } StatefulDrawable* StateListDrawable::setState( Uint32 state ) { - if ( state != mCurrentState || mCurrentDrawable == NULL || - mCurrentDrawable != mDrawables[mCurrentState] ) { + auto current = mDrawables.find( state ); + Drawable* stateDrawable = current != mDrawables.end() ? current->second.get() : nullptr; + if ( state != mCurrentState || mCurrentDrawable != stateDrawable ) { mCurrentState = state; - - auto it = mDrawables.find( state ); - - if ( it != mDrawables.end() ) { - mCurrentDrawable = it->second; - } else { - mCurrentDrawable = NULL; - } + mCurrentDrawable = stateDrawable; } return this; @@ -116,28 +112,21 @@ const Uint32& StateListDrawable::getState() const { Drawable* StateListDrawable::getStateDrawable( const Uint32& state ) { if ( hasDrawableState( state ) ) - return mDrawables[state]; + return mDrawables[state].get(); return NULL; } -StateListDrawable* StateListDrawable::setStateDrawable( const Uint32& state, Drawable* drawable, - bool ownIt ) { +StateListDrawable* StateListDrawable::setStateDrawable( const Uint32& state, + DrawablePtr drawable ) { if ( NULL != drawable ) { - if ( hasDrawableState( state ) && mDrawablesOwnership[mDrawables[state]] ) { + if ( hasDrawableState( state ) && mCurrentDrawable == mDrawables[state].get() ) + mCurrentDrawable = NULL; - if ( mCurrentDrawable == mDrawables[state] ) - mCurrentDrawable = NULL; - - mDrawablesOwnership.erase( mDrawables[state] ); - eeDelete( mDrawables[state] ); - } - - mDrawables[state] = drawable; - mDrawablesOwnership[drawable] = ownIt; + mDrawables[state] = std::move( drawable ); if ( hasDrawableStateColor( state ) ) - drawable->setColor( mDrawableColors[state] ); + mDrawables[state]->setColor( mDrawableColors[state] ); if ( state == mCurrentState ) setState( state ); @@ -194,9 +183,9 @@ bool StateListDrawable::hasDrawableStateColor( const Uint32& state ) const { void StateListDrawable::onColorFilterChange() { for ( auto it = mDrawables.begin(); it != mDrawables.end(); ++it ) { - Drawable* drawable = it->second; - - drawable->setColor( mColor ); + Drawable* drawable = it->second.get(); + if ( drawable ) + drawable->setColor( mColor ); } } diff --git a/src/eepp/graphics/texture.cpp b/src/eepp/graphics/texture.cpp index 3304dde78..a4bead9dc 100644 --- a/src/eepp/graphics/texture.cpp +++ b/src/eepp/graphics/texture.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,16 @@ using namespace EE::Graphics::Private; namespace EE { namespace Graphics { +DrawablePtr Texture::createInstance() const { + TexturePtr texture = TextureFactory::instance()->getTexture( getTextureId() ); + if ( !texture ) + return {}; + TextureDrawablePtr instance = TextureDrawable::New( std::move( texture ) ); + instance->setColor( mColor ); + instance->setPosition( mPosition ); + return instance; +} + Uint32 Texture::getMaximumSize() { static bool checked = false; static GLint size = 0; diff --git a/src/eepp/graphics/texturedrawable.cpp b/src/eepp/graphics/texturedrawable.cpp new file mode 100644 index 000000000..ea7d19d56 --- /dev/null +++ b/src/eepp/graphics/texturedrawable.cpp @@ -0,0 +1,55 @@ +#include + +namespace EE { namespace Graphics { + +TextureDrawablePtr TextureDrawable::New( TexturePtr texture ) { + return makeResource( std::move( texture ) ); +} + +TextureDrawable::TextureDrawable( TexturePtr texture ) : + DrawableResource( Drawable::TEXTUREDRAWABLE, texture ? texture->getName() : "" ), + mTexture( std::move( texture ) ) { + if ( mTexture ) { + mTextureChangeConnection = + mTexture->connectResourceChange( [this]( DrawableResource& ) { onResourceChange(); } ); + } +} + +Sizef TextureDrawable::getSize() { + return mTexture ? mTexture->getSize() : Sizef{}; +} + +Sizef TextureDrawable::getPixelsSize() { + return mTexture ? mTexture->getPixelsSize() : Sizef{}; +} + +void TextureDrawable::draw() { + draw( mPosition ); +} + +void TextureDrawable::draw( const Vector2f& position ) { + draw( position, getPixelsSize() ); +} + +void TextureDrawable::draw( const Vector2f& position, const Sizef& size ) { + if ( mTexture ) + mTexture->drawEx( position.x, position.y, size.x, size.y, 0, Vector2f::One, mColor, mColor, + mColor, mColor ); +} + +bool TextureDrawable::isStateful() { + return false; +} + +DrawablePtr TextureDrawable::createInstance() const { + TextureDrawablePtr instance = New( mTexture ); + instance->setColor( mColor ); + instance->setPosition( mPosition ); + return instance; +} + +const TexturePtr& TextureDrawable::getTexture() const { + return mTexture; +} + +}} // namespace EE::Graphics diff --git a/src/eepp/graphics/texturefactory.cpp b/src/eepp/graphics/texturefactory.cpp index fa166b428..20044115f 100644 --- a/src/eepp/graphics/texturefactory.cpp +++ b/src/eepp/graphics/texturefactory.cpp @@ -320,10 +320,15 @@ void TextureFactory::diagnoseLiveTexturesAtShutdown() { Log::error( "TextureFactory shutdown found %zu externally retained texture(s).", survivors.size() ); + eePRINTL( "TextureFactory shutdown found %zu externally retained texture(s).", + survivors.size() ); for ( const TexturePtr& texture : survivors ) { Log::error( "Texture %llu ('%s') survived shutdown with %zu external owner(s).", static_cast( texture->getTextureId().value() ), texture->getName().c_str(), texture.use_count() - 1 ); + eePRINTL( "Texture %llu ('%s') survived shutdown with %zu external owner(s).", + static_cast( texture->getTextureId().value() ), + texture->getName().c_str(), texture.use_count() - 1 ); texture->deleteTexture(); } diff --git a/src/eepp/graphics/textureregion.cpp b/src/eepp/graphics/textureregion.cpp index aa7305969..b8f372d45 100644 --- a/src/eepp/graphics/textureregion.cpp +++ b/src/eepp/graphics/textureregion.cpp @@ -117,6 +117,15 @@ TextureRegion::~TextureRegion() { clearCache(); } +DrawablePtr TextureRegion::createInstance() const { + auto instance = makeResource( mTexture, mSrcRect, mDestSize, mOffset, mName ); + instance->setOriDestSize( mOriDestSize ); + instance->setPixelDensity( mPixelDensity ); + instance->setColor( mColor ); + instance->setPosition( mPosition ); + return instance; +} + void TextureRegion::setTextureId( ResourceId textureId ) { mTexture = TextureFactory::instance()->getTexture( textureId ); } diff --git a/src/eepp/graphics/triangledrawable.cpp b/src/eepp/graphics/triangledrawable.cpp index c63975dc4..896acb2ad 100644 --- a/src/eepp/graphics/triangledrawable.cpp +++ b/src/eepp/graphics/triangledrawable.cpp @@ -18,6 +18,22 @@ TriangleDrawable::TriangleDrawable( const Vector2f& position, const Sizef& size mPosition = position; } +DrawablePtr TriangleDrawable::createInstance() const { + auto instance = makeResource( mPosition, mSize ); + instance->mTriangle = mTriangle; + instance->mComputedTriangle = mComputedTriangle; + instance->mColors[0] = mColors[0]; + instance->mColors[1] = mColors[1]; + instance->mColors[2] = mColors[2]; + instance->mCustomColors = mCustomColors; + instance->mFillMode = mFillMode; + instance->mBlendMode = mBlendMode; + instance->mLineWidth = mLineWidth; + instance->mSmooth = mSmooth; + instance->mColor = mColor; + return instance; +} + Sizef TriangleDrawable::getSize() { return mTriangle.getSize(); } diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index dd6e331a2..2f1818200 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -667,7 +667,7 @@ UIWidget* UIAbstractTableView::updateCell( const Vector2& posIndex, const cell->setIcon( icon.asDrawable() ); } else if ( icon.is( Variant::Type::Icon ) && icon.asIcon() ) { isVisible = true; - cell->setIcon( icon.asIcon()->getSize( mIconSize ) ); + cell->setIcon( icon.asIcon()->createDrawable( mIconSize ) ); } if ( cell->hasIcon() ) cell->getIcon()->setVisible( isVisible ); @@ -891,7 +891,7 @@ void UIAbstractTableView::onSortColumn( const size_t& colIndex ) { UIImage* image = columnData( model->keyColumn() ).widget->getExtraInnerWidget()->asType(); image->setForegroundFillEnabled( false ); - image->setDrawable( nullptr ); + image->setDrawable( DrawablePtr{} ); } SortOrder sortOrder = model->sortOrder() == SortOrder::Ascending ? SortOrder::Descending : SortOrder::Ascending; @@ -904,10 +904,10 @@ void UIAbstractTableView::onSortColumn( const size_t& colIndex ) { if ( image->getForeground() ) image->getForeground()->setAlpha( 255 ); if ( image && image->getForeground() == nullptr ) { - Drawable* icon = mUISceneNode->findIconDrawable( + DrawablePtr icon = mUISceneNode->findIconDrawable( sortOrder == SortOrder::Ascending ? "arrow-down" : "arrow-up", mSortIconSize ); if ( icon ) - image->setDrawable( icon ); + image->setDrawable( std::move( icon ) ); } model->sort( colIndex, sortOrder ); } diff --git a/src/eepp/ui/css/drawableimageparser.cpp b/src/eepp/ui/css/drawableimageparser.cpp index 67d40446e..856fc289a 100644 --- a/src/eepp/ui/css/drawableimageparser.cpp +++ b/src/eepp/ui/css/drawableimageparser.cpp @@ -153,27 +153,23 @@ bool DrawableImageParser::exists( const std::string& name ) const { return mFuncs.find( name ) != mFuncs.end(); } -Drawable* DrawableImageParser::createDrawable( const std::string& value, const Sizef& size, - bool& ownIt, UINode* node ) { +DrawablePtr DrawableImageParser::createDrawable( const std::string& value, const Sizef& size, + UINode* node ) { FunctionString functionType = FunctionString::parse( value ); - Drawable* res = NULL; - ownIt = false; if ( "none" == value ) - return NULL; + return {}; if ( !functionType.isEmpty() ) { if ( exists( functionType.getName() ) ) - return mFuncs[functionType.getName()]( functionType, size, ownIt, node ); - } else if ( NULL != ( res = DrawableSearcher::searchByName( - value, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ) ) ) { - if ( res->getDrawableType() == Drawable::SPRITE ) - ownIt = true; - return res; + return mFuncs[functionType.getName()]( functionType, size, node ); + } else if ( DrawablePtr drawable = DrawableSearcher::searchByName( + value, false, node->getUISceneNode()->getReferer(), + node->getUISceneNode()->getResourceScope().get() ) ) { + return drawable; } - return res; + return {}; } void DrawableImageParser::addParser( const std::string& name, @@ -190,11 +186,11 @@ void DrawableImageParser::addParser( const std::string& name, void DrawableImageParser::registerBaseParsers() { // Shared parsing logic for linear-gradient and repeating-linear-gradient - auto parseGradient = []( const FunctionString& functionType, bool& ownIt, UINode* node, - bool repeating ) -> Drawable* { + auto parseGradient = []( const FunctionString& functionType, UINode* node, + bool repeating ) -> DrawablePtr { const auto& params( functionType.getParameters() ); if ( params.size() < 2 ) - return NULL; + return {}; size_t paramIdx = 0; Float angle = 180.f; /* default: to bottom */ @@ -332,7 +328,7 @@ void DrawableImageParser::registerBaseParsers() { colorStopCount++; } if ( colorStopCount < 2 ) - return NULL; + return {}; } // Sort by position. Hints with the same position as a color stop are @@ -451,34 +447,33 @@ void DrawableImageParser::registerBaseParsers() { } if ( stops.size() < 2 ) - return NULL; + return {}; - LinearGradientDrawable* drawable = - repeating ? LinearGradientDrawable::NewRepeating() : LinearGradientDrawable::New(); + auto drawable = makeResource( + repeating ? Drawable::REPEATINGLINEARGRADIENT : Drawable::LINEARGRADIENT ); drawable->setColorStops( std::move( stops ) ); drawable->setAngle( angle ); - ownIt = true; return drawable; }; mFuncs["linear-gradient"] = [parseGradient]( const FunctionString& functionType, - const Sizef& /*size*/, bool& ownIt, - UINode* node ) -> Drawable* { - return parseGradient( functionType, ownIt, node, false ); + const Sizef& /*size*/, + UINode* node ) -> DrawablePtr { + return parseGradient( functionType, node, false ); }; mFuncs["repeating-linear-gradient"] = [parseGradient]( const FunctionString& functionType, - const Sizef& /*size*/, bool& ownIt, - UINode* node ) -> Drawable* { - return parseGradient( functionType, ownIt, node, true ); + const Sizef& /*size*/, + UINode* node ) -> DrawablePtr { + return parseGradient( functionType, node, true ); }; // Shared parsing logic for radial-gradient and repeating-radial-gradient - auto parseRadialGradient = []( const FunctionString& functionType, bool& ownIt, UINode* node, - bool repeating ) -> Drawable* { + auto parseRadialGradient = []( const FunctionString& functionType, UINode* node, + bool repeating ) -> DrawablePtr { const auto& params( functionType.getParameters() ); if ( params.size() < 2 ) - return NULL; + return {}; size_t paramIdx = 0; RadialGradientDrawable::ShapeType shape = RadialGradientDrawable::CIRCLE; @@ -558,7 +553,7 @@ void DrawableImageParser::registerBaseParsers() { colorStopCount++; } if ( colorStopCount < 2 ) - return NULL; + return {}; } std::sort( gradientStops.begin(), gradientStops.end(), @@ -672,37 +667,36 @@ void DrawableImageParser::registerBaseParsers() { } if ( stops.size() < 2 ) - return NULL; + return {}; - RadialGradientDrawable* drawable = - repeating ? RadialGradientDrawable::NewRepeating() : RadialGradientDrawable::New(); + auto drawable = makeResource( + repeating ? Drawable::REPEATINGRADIALGRADIENT : Drawable::RADIALGRADIENT ); drawable->setColorStops( std::move( stops ) ); drawable->setShape( shape ); drawable->setExtent( extent ); drawable->setCenter( center ); - ownIt = true; return drawable; }; mFuncs["radial-gradient"] = [parseRadialGradient]( const FunctionString& functionType, - const Sizef& /*size*/, bool& ownIt, - UINode* node ) -> Drawable* { - return parseRadialGradient( functionType, ownIt, node, false ); + const Sizef& /*size*/, + UINode* node ) -> DrawablePtr { + return parseRadialGradient( functionType, node, false ); }; mFuncs["repeating-radial-gradient"] = [parseRadialGradient]( const FunctionString& functionType, - const Sizef& /*size*/, bool& ownIt, - UINode* node ) -> Drawable* { - return parseRadialGradient( functionType, ownIt, node, true ); + const Sizef& /*size*/, + UINode* node ) -> DrawablePtr { + return parseRadialGradient( functionType, node, true ); }; - mFuncs["circle"] = []( const FunctionString& functionType, const Sizef& size, bool& ownIt, - UINode* node ) -> Drawable* { + mFuncs["circle"] = []( const FunctionString& functionType, const Sizef& size, + UINode* node ) -> DrawablePtr { if ( functionType.getParameters().size() < 1 ) { - return NULL; + return {}; } - CircleDrawable* drawable = CircleDrawable::New(); + auto drawable = makeResource(); const auto& params( functionType.getParameters() ); @@ -723,17 +717,16 @@ void DrawableImageParser::registerBaseParsers() { } drawable->setOffset( drawable->getSize() / 2.f ); - ownIt = true; return drawable; }; - mFuncs["rectangle"] = []( const FunctionString& functionType, const Sizef& size, bool& ownIt, - UINode* node ) -> Drawable* { + mFuncs["rectangle"] = []( const FunctionString& functionType, const Sizef& size, + UINode* node ) -> DrawablePtr { if ( functionType.getParameters().size() < 1 ) { - return NULL; + return {}; } - RectangleDrawable* drawable = RectangleDrawable::New(); + auto drawable = makeResource(); RectColors rectColors; std::vector colors; @@ -780,22 +773,19 @@ void DrawableImageParser::registerBaseParsers() { rectColors.BottomRight = colors[2]; rectColors.TopRight = colors[3]; drawable->setRectColors( rectColors ); - ownIt = true; return drawable; - } else { - eeSAFE_DELETE( drawable ); } - return drawable; + return {}; }; - mFuncs["triangle"] = []( const FunctionString& functionType, const Sizef& size, bool& ownIt, - UINode* node ) -> Drawable* { + mFuncs["triangle"] = []( const FunctionString& functionType, const Sizef& size, + UINode* node ) -> DrawablePtr { if ( functionType.getParameters().size() < 2 ) { - return NULL; + return {}; } - TriangleDrawable* drawable = TriangleDrawable::New(); + auto drawable = makeResource(); std::vector colors; std::vector vertices; @@ -850,22 +840,19 @@ void DrawableImageParser::registerBaseParsers() { } drawable->setTriangle( triangle ); - ownIt = true; return drawable; - } else { - eeSAFE_DELETE( drawable ); } - return drawable; + return {}; }; - mFuncs["poly"] = []( const FunctionString& functionType, const Sizef& size, bool& ownIt, - UINode* node ) -> Drawable* { + mFuncs["poly"] = []( const FunctionString& functionType, const Sizef& size, + UINode* node ) -> DrawablePtr { if ( functionType.getParameters().size() < 2 ) { - return NULL; + return {}; } - ConvexShapeDrawable* drawable = ConvexShapeDrawable::New(); + auto drawable = makeResource(); std::vector colors; std::vector vertices; @@ -909,26 +896,24 @@ void DrawableImageParser::registerBaseParsers() { drawable->addPoint( vertices[i], colors[i % colors.size()] ); } - ownIt = true; return drawable; - } else { - eeSAFE_DELETE( drawable ); } - return drawable; + return {}; }; - mFuncs["url"] = []( const FunctionString& functionType, const Sizef& /*size*/, bool& /*ownIt*/, - UINode* node ) -> Drawable* { + mFuncs["url"] = []( const FunctionString& functionType, const Sizef& /*size*/, + UINode* node ) -> DrawablePtr { if ( functionType.getParameters().size() < 1 ) - return NULL; + return {}; const auto& param = functionType.getParameters().at( 0 ); if ( functionType.getName() == "url" && !param.empty() && param[0] != '@' && !String::startsWith( param, "data:image/" ) ) { - return DrawableSearcher::searchByName( + DrawablePtr drawable = DrawableSearcher::searchByName( node->getUISceneNode()->solveRelativePath( param ).toString(), false, node->getUISceneNode()->getReferer(), node->getUISceneNode()->getResourceScope().get() ); + return drawable; } else if ( functionType.getParameters().size() > 1 && String::startsWith( param, "data:image/" ) ) { auto cparam = functionType.getParameters().at( 0 ); @@ -936,16 +921,19 @@ void DrawableImageParser::registerBaseParsers() { cparam += ','; cparam += functionType.getParameters().at( i ); } - return DrawableSearcher::searchByName( + DrawablePtr drawable = DrawableSearcher::searchByName( cparam, false, node->getUISceneNode()->getReferer(), node->getUISceneNode()->getResourceScope().get() ); + return drawable; } - return DrawableSearcher::searchByName( param, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ); + DrawablePtr drawable = DrawableSearcher::searchByName( + param, false, node->getUISceneNode()->getReferer(), + node->getUISceneNode()->getResourceScope().get() ); + return drawable; }; - mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size, bool&, - UINode* node ) -> Drawable* { + mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size, + UINode* node ) -> DrawablePtr { auto* uiScene = SceneManager::instance()->getUISceneNode(); const auto& params = functionType.getParameters(); if ( params.size() < 2 ) @@ -955,8 +943,8 @@ void DrawableImageParser::registerBaseParsers() { node->convertLength( length, size.getWidth() ) ); }; - mFuncs["glyph"] = []( const FunctionString& functionType, const Sizef& size, bool&, - UINode* node ) -> Drawable* { + mFuncs["glyph"] = []( const FunctionString& functionType, const Sizef& size, + UINode* node ) -> DrawablePtr { const auto& params = functionType.getParameters(); if ( params.size() < 3 ) return nullptr; @@ -976,8 +964,9 @@ void DrawableImageParser::registerBaseParsers() { } else if ( String::fromString( value, buffer ) ) { codePoint = value; } - return font->getGlyphDrawable( codePoint, - node->convertLength( params[1], size.getWidth() ) ); + Drawable* drawable = font->getGlyphDrawable( + codePoint, node->convertLength( params[1], size.getWidth() ) ); + return drawable ? drawable->createInstance() : DrawablePtr{}; }; } diff --git a/src/eepp/ui/lineargradientdrawable.cpp b/src/eepp/ui/lineargradientdrawable.cpp index 3c42db598..2340d8f57 100644 --- a/src/eepp/ui/lineargradientdrawable.cpp +++ b/src/eepp/ui/lineargradientdrawable.cpp @@ -18,6 +18,16 @@ LinearGradientDrawable* LinearGradientDrawable::NewRepeating() { LinearGradientDrawable::LinearGradientDrawable( Graphics::Drawable::Type drawableType ) : Drawable( drawableType ) {} +DrawablePtr LinearGradientDrawable::createInstance() const { + auto instance = makeResource( mDrawableType ); + instance->mColorStops = mColorStops; + instance->mAngle = mAngle; + instance->mSize = mSize; + instance->mColor = mColor; + instance->mPosition = mPosition; + return instance; +} + Sizef LinearGradientDrawable::getSize() { return mSize; } diff --git a/src/eepp/ui/radialgradientdrawable.cpp b/src/eepp/ui/radialgradientdrawable.cpp index b506e35d0..05a98c363 100644 --- a/src/eepp/ui/radialgradientdrawable.cpp +++ b/src/eepp/ui/radialgradientdrawable.cpp @@ -18,6 +18,18 @@ RadialGradientDrawable* RadialGradientDrawable::NewRepeating() { RadialGradientDrawable::RadialGradientDrawable( Graphics::Drawable::Type drawableType ) : Drawable( drawableType ) {} +DrawablePtr RadialGradientDrawable::createInstance() const { + auto instance = makeResource( mDrawableType ); + instance->mColorStops = mColorStops; + instance->mShape = mShape; + instance->mExtent = mExtent; + instance->mCenter = mCenter; + instance->mSize = mSize; + instance->mColor = mColor; + instance->mPosition = mPosition; + return instance; +} + Sizef RadialGradientDrawable::getSize() { return mSize; } @@ -75,7 +87,6 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) std::sort( stops.begin(), stops.end(), []( const ColorStop& a, const ColorStop& b ) { return a.value < b.value; } ); - const int SEGMENTS = 48; Float angleStep = 2.f * EE_PI / (Float)SEGMENTS; @@ -133,16 +144,14 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float frac1 = ( clip1 - p0 ) / bw; const Color& sc0 = stops[i].color; const Color& sc1 = stops[i + 1].color; - Color cc0( - (Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ), - (Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ), - (Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ), - (Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) ); - Color cc1( - (Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ), - (Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ), - (Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ), - (Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) ); + Color cc0( (Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ), + (Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ), + (Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ), + (Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) ); + Color cc1( (Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ), + (Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ), + (Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ), + (Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) ); Color fc0 = ( mColor.a == 255 ) ? cc0 : Color( cc0 ).blendAlpha( mColor.a ); Color fc1 = ( mColor.a == 255 ) ? cc1 : Color( cc1 ).blendAlpha( mColor.a ); @@ -175,9 +184,8 @@ void RadialGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float cj = cosVals[j], sj = sinVals[j]; Float cj1 = cosVals[j + 1], sj1 = sinVals[j + 1]; - sBR->batchQuadFree( cx + r0 * cj + posX, cy + r0 * sj + posY, - cx + r1 * cj + posX, cy + r1 * sj + posY, - cx + r1 * cj1 + posX, cy + r1 * sj1 + posY, + sBR->batchQuadFree( cx + r0 * cj + posX, cy + r0 * sj + posY, cx + r1 * cj + posX, + cy + r1 * sj + posY, cx + r1 * cj1 + posX, cy + r1 * sj1 + posY, cx + r0 * cj1 + posX, cy + r0 * sj1 + posY ); } } diff --git a/src/eepp/ui/tools/uicolorpicker.cpp b/src/eepp/ui/tools/uicolorpicker.cpp index 8279e432b..1f1b21bc0 100644 --- a/src/eepp/ui/tools/uicolorpicker.cpp +++ b/src/eepp/ui/tools/uicolorpicker.cpp @@ -288,7 +288,8 @@ UIColorPicker::UIColorPicker( UIWindow* attachTo, const UIColorPicker::ColorPick mRoot->on( Event::OnLayoutUpdate, [this]( const Event* ) { if ( mHuePicker->getDrawable() == nullptr ) { mHuePicker->setDrawable( createHueTexture( mHuePicker->getPixelsSize() ) ); - mCurrentColor->setBackgroundDrawable( TextureRegion::New( createGridTexture() ), true ); + mCurrentColor->setBackgroundDrawable( + makeResource( createGridTexture() ) ); mCurrentColor->setBackgroundRepeat( "repeat" ); updateAll(); } @@ -404,9 +405,9 @@ TexturePtr UIColorPicker::createGridTexture() { } void UIColorPicker::updateColorPicker() { - DrawableGroup* colorRectangle = DrawableGroup::New(); + auto colorRectangle = DrawableGroup::New(); - RectangleDrawable* rectDrawable = RectangleDrawable::New(); + auto rectDrawable = makeResource(); RectColors rectColors; rectDrawable->setSize( mColorPicker->getPixelsSize() ); @@ -417,7 +418,7 @@ void UIColorPicker::updateColorPicker() { rectDrawable->setRectColors( rectColors ); colorRectangle->addDrawable( rectDrawable ); - rectDrawable = RectangleDrawable::New(); + rectDrawable = makeResource(); rectDrawable->setSize( mColorPicker->getPixelsSize() ); rectColors.TopLeft = Color::Transparent; rectColors.BottomLeft = Color::Black; @@ -426,7 +427,7 @@ void UIColorPicker::updateColorPicker() { rectDrawable->setRectColors( rectColors ); colorRectangle->addDrawable( rectDrawable ); - mColorPicker->setDrawable( colorRectangle, true ); + mColorPicker->setDrawable( std::move( colorRectangle ) ); } void UIColorPicker::updateGuideLines() { diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp index 3fabb31ad..e58637438 100644 --- a/src/eepp/ui/tools/uidiffview.cpp +++ b/src/eepp/ui/tools/uidiffview.cpp @@ -67,12 +67,12 @@ static Sprite* setImageViewerImage( UIImageViewer* viewer, Image* image ) { auto sprite = Sprite::New(); sprite->createStatic( texture ); - sprite->setAsTextureRegionOwner( true ); + Sprite* spritePtr = sprite.get(); viewer->reset(); - viewer->getImage()->setDrawable( sprite, true ); + viewer->getImage()->setDrawable( std::move( sprite ) ); setImageViewerImageSize( viewer ); - return sprite; + return spritePtr; } UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText, diff --git a/src/eepp/ui/tools/uifontpickerdialog.cpp b/src/eepp/ui/tools/uifontpickerdialog.cpp index 64d594144..72bdc158f 100644 --- a/src/eepp/ui/tools/uifontpickerdialog.cpp +++ b/src/eepp/ui/tools/uifontpickerdialog.cpp @@ -281,21 +281,21 @@ void UIFontPickerDialog::setTheme( UITheme* theme ) { UIWindow::setTheme( theme ); if ( mButtonOK ) { - if ( Drawable* icon = + if ( DrawablePtr icon = getUISceneNode()->findIconDrawable( "ok", PixelDensity::dpToPxI( 16 ) ) ) - mButtonOK->setIcon( icon ); + mButtonOK->setIcon( std::move( icon ) ); } if ( mButtonCancel ) { - if ( Drawable* icon = + if ( DrawablePtr icon = getUISceneNode()->findIconDrawable( "cancel", PixelDensity::dpToPxI( 16 ) ) ) - mButtonCancel->setIcon( icon ); + mButtonCancel->setIcon( std::move( icon ) ); } if ( mButtonBrowse ) { - if ( Drawable* icon = getUISceneNode()->findIconDrawable( "document-open", - PixelDensity::dpToPxI( 16 ) ) ) - mButtonBrowse->setIcon( icon ); + if ( DrawablePtr icon = getUISceneNode()->findIconDrawable( "document-open", + PixelDensity::dpToPxI( 16 ) ) ) + mButtonBrowse->setIcon( std::move( icon ) ); } onThemeLoaded(); diff --git a/src/eepp/ui/tools/uiimageviewer.cpp b/src/eepp/ui/tools/uiimageviewer.cpp index e6d0d7c37..0845ed089 100644 --- a/src/eepp/ui/tools/uiimageviewer.cpp +++ b/src/eepp/ui/tools/uiimageviewer.cpp @@ -155,7 +155,7 @@ void UIImageViewer::loadImageAsync( std::string_view path, bool isContents, bool if ( format == Image::Format::Unknown ) return; - Sprite* image = nullptr; + DrawablePtr image; if ( mClosing ) return; @@ -165,18 +165,16 @@ void UIImageViewer::loadImageAsync( std::string_view path, bool isContents, bool reinterpret_cast( path.c_str() ), path.size() ) : TextureFactory::instance()->loadFromFile( path ); - Sprite* sprite = Sprite::New(); + SpritePtr sprite = Sprite::New(); sprite->createStatic( tex ); - sprite->setAsTextureRegionOwner( true ); - image = sprite; + image = std::move( sprite ); } else { IOStream* stream = isContents ? (IOStream*)new IOStreamMemory( path.c_str(), path.size() ) : (IOStream*)new IOStreamFile( path ); - Sprite* sprite = Sprite::fromGif( *stream ); - sprite->setAsTextureRegionOwner( true ); + SpritePtr sprite = Sprite::fromGif( *stream ); sprite->setAutoAnimate( false ); - image = sprite; + image = std::move( sprite ); delete stream; } @@ -188,7 +186,7 @@ void UIImageViewer::loadImageAsync( std::string_view path, bool isContents, bool mCurFileType = format; runOnMainThread( [this, image] { - mImage->setDrawable( image, true ); + mImage->setDrawable( image ); updateTextDisplay(); auto s( image->getPixelsSize() ); auto scale( s.x > mSize.x || s.y > mSize.y @@ -210,7 +208,7 @@ void UIImageViewer::onSizeChange() { } void UIImageViewer::reset() { - mImage->setDrawable( nullptr )->setVisible( false ); + mImage->setDrawable( DrawablePtr{} )->setVisible( false ); } Uint32 UIImageViewer::onMessage( const NodeMessage* msg ) { @@ -311,7 +309,7 @@ Uint32 UIImageViewer::onKeyDown( const KeyEvent& event ) { } else if ( event.getKeyCode() == KEY_T ) { resetImageView(); } else if ( event.getKeyCode() == KEY_X ) { - auto sprite = static_cast( mImage->getDrawable() ); + auto sprite = static_cast( mImage->getDrawable().get() ); auto mode = sprite->getRenderMode(); if ( mode == RENDER_NORMAL ) mode = RENDER_FLIPPED; @@ -324,7 +322,7 @@ Uint32 UIImageViewer::onKeyDown( const KeyEvent& event ) { sprite->setRenderMode( mode ); invalidateDraw(); } else if ( event.getKeyCode() == KEY_C ) { - auto sprite = static_cast( mImage->getDrawable() ); + auto sprite = static_cast( mImage->getDrawable().get() ); auto mode = sprite->getRenderMode(); if ( mode == RENDER_NORMAL ) mode = RENDER_MIRROR; diff --git a/src/eepp/ui/tools/uitabwidgetsplitter.cpp b/src/eepp/ui/tools/uitabwidgetsplitter.cpp index c7acdbfc3..b5b6df8bf 100644 --- a/src/eepp/ui/tools/uitabwidgetsplitter.cpp +++ b/src/eepp/ui/tools/uitabwidgetsplitter.cpp @@ -912,7 +912,7 @@ void UITabWidgetSplitter::unserializeNode( const nlohmann::json& j, UITabWidget* !result.title.empty() ? result.title : file.value( "title", "" ); auto [tab, _] = createWidgetInTabWidget( curTabWidget, result.widget, title ); if ( result.icon ) - tab->setIcon( result.icon ); + tab->setIcon( result.icon->createInstance() ); } } if ( curTabWidget->getTabCount() > 0 ) { diff --git a/src/eepp/ui/tools/uitextureviewer.cpp b/src/eepp/ui/tools/uitextureviewer.cpp index 672c684d8..d81c4440a 100644 --- a/src/eepp/ui/tools/uitextureviewer.cpp +++ b/src/eepp/ui/tools/uitextureviewer.cpp @@ -22,12 +22,12 @@ void UITextureViewer::setImage( TexturePtr texture ) { return; mSelectedTexture = std::move( texture ); mImageLayout->setEnabled( true )->setVisible( true ); - imageView->setDrawable( mSelectedTexture.get() ); + imageView->setDrawable( mSelectedTexture ); } void UITextureViewer::clearSelectedTexture() { if ( UIImage* imageView = mImageLayout->findByType( UI_TYPE_IMAGE ) ) - imageView->setDrawable( nullptr ); + imageView->setDrawable( DrawablePtr{} ); mSelectedTexture.reset(); mImageLayout->setEnabled( false )->setVisible( false ); } @@ -110,7 +110,7 @@ void UITextureViewer::insertTexture( const TextureRegistryRecord& record ) { UIImage* img = UIImage::New(); std::string uid( String::format( "texture-%llu", static_cast( record.id.value() ) ) ); - img->setDrawable( texture.get() ) + img->setDrawable( texture ) ->setScaleType( UIScaleType::FitInside ) ->setClasses( { "texture-preview", uid } ) ->setTooltipText( getTextureDescription( texture.get() ) ) diff --git a/src/eepp/ui/tools/uiwidgetinspector.cpp b/src/eepp/ui/tools/uiwidgetinspector.cpp index df3853d43..ebbc8ab91 100644 --- a/src/eepp/ui/tools/uiwidgetinspector.cpp +++ b/src/eepp/ui/tools/uiwidgetinspector.cpp @@ -148,11 +148,11 @@ UIWindow* UIWidgetInspector::create( UISceneNode* sceneNode, const Float& menuIc UIPushButton* button = cont->find( "pick_widget" ); if ( button->getIcon() == nullptr ) { - Drawable* cursorPointer = button->getUISceneNode()->findIconDrawable( + DrawablePtr cursorPointer = button->getUISceneNode()->findIconDrawable( "cursor-pointer", PixelDensity::dpToPx( 16 ) ); if ( cursorPointer ) - button->setIcon( cursorPointer, true ); + button->setIcon( std::move( cursorPointer ) ); } button->on( Event::MouseClick, [sceneNode, nodeTree, computedView]( const Event* event ) { diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 34a515391..7c8cd3f98 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -484,6 +484,17 @@ void UICodeEditor::scheduledUpdate( const Time& ) { if ( !mVisible ) return; + if ( mLocked && mDisplayLockedIcon ) { + if ( mFileLockIcon == nullptr && !mFileLockIconName.empty() ) + mFileLockIcon = getUISceneNode()->findIcon( mFileLockIconName ); + const int iconSize = PixelDensity::dpToPxI( 16 ); + if ( mFileLockIcon && + ( mFileLockDrawable == nullptr || mFileLockDrawableSize != iconSize ) ) { + mFileLockDrawable = mFileLockIcon->createDrawable( iconSize ); + mFileLockDrawableSize = iconSize; + } + } + if ( mDoc && !mDoc->isLoading() && !mDoc->isEmpty() && !mDoc->getSyntaxDefinition().getPatterns().empty() && mDoc->getHighlighter()->updateDirty( getVisibleLinesCount() ) ) { @@ -1146,27 +1157,21 @@ void UICodeEditor::updateIMELocation() { } void UICodeEditor::drawLockedIcon( const Vector2f start ) { - if ( mFileLockIcon == nullptr && !mFileLockIconName.empty() ) - mFileLockIcon = getUISceneNode()->findIcon( mFileLockIconName ); - if ( mFileLockIcon == nullptr ) + if ( mFileLockDrawable == nullptr ) return; - Drawable* fileLockIcon = mFileLockIcon->getSize( PixelDensity::dpToPxI( 16 ) ); - if ( fileLockIcon == nullptr ) - return; - - Float w = fileLockIcon->getPixelsSize().getWidth(); + Float w = mFileLockDrawable->getPixelsSize().getWidth(); Float posX = mMinimapEnabled ? getMinimapRect( getScreenStart() ).Left - w : ( start.x + mSize.getWidth() - ( mVScrollBar->isVisible() ? mVScrollBar->getPixelsSize().getWidth() : 0 ) ) - mPadding.Right - w; - Color col( fileLockIcon->getColor() ); - fileLockIcon->setColor( Color( mFontStyleConfig.getFontColor() ).blendAlpha( mAlpha ) ); + Color col( mFileLockDrawable->getColor() ); + mFileLockDrawable->setColor( Color( mFontStyleConfig.getFontColor() ).blendAlpha( mAlpha ) ); Float margin = PixelDensity::dpToPxI( 4 ); - fileLockIcon->draw( { posX - margin, start.y + margin } ); - fileLockIcon->setColor( col ); + mFileLockDrawable->draw( { posX - margin, start.y + margin } ); + mFileLockDrawable->setColor( col ); } size_t UICodeEditor::getTotalVisibleLines() const { @@ -3533,11 +3538,9 @@ void UICodeEditor::updateGlyphWidth() { invalidateLineWrapMaxWidth( false ); } -Drawable* UICodeEditor::findIcon( const std::string& name ) { +DrawablePtr UICodeEditor::findIcon( const std::string& name ) { UIIcon* icon = getUISceneNode()->findIcon( name ); - if ( icon ) - return icon->getSize( mMenuIconSize ); - return nullptr; + return icon ? icon->createDrawable( mMenuIconSize ) : DrawablePtr{}; } const bool& UICodeEditor::getColorPreview() const { @@ -3755,6 +3758,8 @@ void UICodeEditor::setFileLockIconName( const std::string& fileLockIconName ) { if ( mFileLockIconName != fileLockIconName ) { mFileLockIconName = fileLockIconName; mFileLockIcon = nullptr; + mFileLockDrawable.reset(); + mFileLockDrawableSize = 0; } } @@ -4610,7 +4615,7 @@ void UICodeEditor::drawLineNumbers( const DocumentLineRange& lineRange, const Ve if ( mFoldsAlwaysVisible || mFoldsVisible || currentLineHasFold ) { if ( ( isFolded && mFoldedDrawable ) || ( !isFolded && mFoldedDrawable ) ) { - Drawable* drawable = isFolded ? mFoldedDrawable : mFoldDrawable; + Drawable* drawable = ( isFolded ? mFoldedDrawable : mFoldDrawable ).get(); GlyphDrawable::DrawMode oldMode; if ( drawable->getDrawableType() == Drawable::Type::GLYPH ) { @@ -5462,20 +5467,20 @@ void UICodeEditor::setShowFoldingRegion( bool showFoldingRegion ) { } } -Drawable* UICodeEditor::getFoldDrawable() const { +const DrawablePtr& UICodeEditor::getFoldDrawable() const { return mFoldDrawable; } -void UICodeEditor::setFoldDrawable( Drawable* foldDrawable ) { - mFoldDrawable = foldDrawable; +void UICodeEditor::setFoldDrawable( DrawablePtr foldDrawable ) { + mFoldDrawable = std::move( foldDrawable ); } -Drawable* UICodeEditor::getFoldedDrawable() const { +const DrawablePtr& UICodeEditor::getFoldedDrawable() const { return mFoldedDrawable; } -void UICodeEditor::setFoldedDrawable( Drawable* foldedDrawable ) { - mFoldedDrawable = foldedDrawable; +void UICodeEditor::setFoldedDrawable( DrawablePtr foldedDrawable ) { + mFoldedDrawable = std::move( foldedDrawable ); } bool UICodeEditor::getFoldsAlwaysVisible() const { diff --git a/src/eepp/ui/uiconsole.cpp b/src/eepp/ui/uiconsole.cpp index 9d2064410..568053334 100644 --- a/src/eepp/ui/uiconsole.cpp +++ b/src/eepp/ui/uiconsole.cpp @@ -1226,11 +1226,9 @@ void UIConsole::onDocumentSaved( TextDocument* ) {} void UIConsole::onDocumentMoved( TextDocument* ) {} -Drawable* UIConsole::findIcon( const std::string& name ) { +DrawablePtr UIConsole::findIcon( const std::string& name ) { UIIcon* icon = getUISceneNode()->findIcon( name ); - if ( icon ) - return icon->getSize( mMenuIconSize ); - return nullptr; + return icon ? icon->createDrawable( mMenuIconSize ) : DrawablePtr{}; } void UIConsole::copySelection() { diff --git a/src/eepp/ui/uifiledialog.cpp b/src/eepp/ui/uifiledialog.cpp index fd30c2b44..b137dbda5 100644 --- a/src/eepp/ui/uifiledialog.cpp +++ b/src/eepp/ui/uifiledialog.cpp @@ -341,31 +341,31 @@ void UIFileDialog::setTheme( UITheme* Theme ) { mFile->setTheme( Theme ); mFiletype->setTheme( Theme ); - Drawable* icon = getUISceneNode()->findIconDrawable( "go-up", PixelDensity::dpToPxI( 16 ) ); + DrawablePtr icon = getUISceneNode()->findIconDrawable( "go-up", PixelDensity::dpToPxI( 16 ) ); if ( icon ) { mButtonUp->setText( "" ); - mButtonUp->setIcon( icon ); + mButtonUp->setIcon( std::move( icon ) ); mButtonUp->setTooltipText( i18n( "uifiledialog_go_up", "Up" ) ); } icon = getUISceneNode()->findIconDrawable( "folder-add", PixelDensity::dpToPxI( 16 ) ); if ( icon ) { mButtonNewFolder->setText( "" ); - mButtonNewFolder->setIcon( icon ); + mButtonNewFolder->setIcon( std::move( icon ) ); mButtonNewFolder->setTooltipText( i18n( "uifiledialog_new_folder", "New Folder" ) ); } icon = getUISceneNode()->findIconDrawable( "list-view", PixelDensity::dpToPxI( 16 ) ); if ( icon ) { mButtonListView->setText( "" ); - mButtonListView->setIcon( icon ); + mButtonListView->setIcon( std::move( icon ) ); mButtonListView->setTooltipText( i18n( "uifiledialog_list", "List" ) ); } icon = getUISceneNode()->findIconDrawable( "table-view", PixelDensity::dpToPxI( 16 ) ); if ( icon ) { mButtonTableView->setText( "" ); - mButtonTableView->setIcon( icon ); + mButtonTableView->setIcon( std::move( icon ) ); mButtonTableView->setTooltipText( i18n( "uifiledialog_table", "Table" ) ); } diff --git a/src/eepp/ui/uiicon.cpp b/src/eepp/ui/uiicon.cpp index e3a95533c..323929178 100644 --- a/src/eepp/ui/uiicon.cpp +++ b/src/eepp/ui/uiicon.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace EE { namespace UI { @@ -16,39 +17,48 @@ const std::string& UIIcon::getName() const { return mName; } -Drawable* UIIcon::getSize( const int& size ) const { +const DrawablePtr& UIIcon::getSource( const int& size ) const { + static const DrawablePtr empty; auto it = mSizes.find( size ); if ( it != mSizes.end() ) return it->second; - int distance = UINT32_MAX; - Drawable* closest = nullptr; + int distance = std::numeric_limits::max(); + const DrawablePtr* closest = nullptr; for ( const auto& sit : mSizes ) { int diff = abs( sit.first - size ); if ( diff < distance ) { distance = diff; - closest = sit.second; + closest = &sit.second; } } - return closest; + return closest ? *closest : empty; } -void UIIcon::setSize( const int& size, Drawable* drawable ) { - mSizes[size] = drawable; +DrawablePtr UIIcon::createDrawable( const int& size ) const { + const DrawablePtr& source = getSource( size ); + return source ? source->createInstance() : DrawablePtr{}; +} + +void UIIcon::setSource( const int& size, DrawablePtr drawable ) { + mSizes[size] = std::move( drawable ); } UIIcon* UIGlyphIcon::New( const std::string& name, FontTrueType* font, const Uint32& codePoint ) { return eeNew( UIGlyphIcon, ( name, font, codePoint ) ); } -Drawable* UIGlyphIcon::getSize( const int& size ) const { +const DrawablePtr& UIGlyphIcon::getSource( const int& size ) const { + static const DrawablePtr empty; if ( !mFont ) - return nullptr; + return empty; auto it = mSizes.find( size ); if ( it != mSizes.end() ) return it->second; GlyphDrawable* drawable = mFont->getGlyphDrawable( mCodePoint, size ); - const_cast( this )->setSize( size, drawable ); - return drawable; + if ( !drawable ) + return empty; + const_cast( this )->setSource( size, drawable->createInstance() ); + return UIIcon::getSource( size ); } UIGlyphIcon::UIGlyphIcon( const std::string& name, FontTrueType* font, const Uint32& codePoint ) : @@ -74,10 +84,11 @@ UIIcon* UISVGIcon::New( const std::string& name, const std::string& svgXML ) { UISVGIcon::~UISVGIcon() {} -Drawable* UISVGIcon::getSize( const int& size ) const { - auto it = mSVGs.find( size ); - if ( it != mSVGs.end() ) - return it->second.get(); +const DrawablePtr& UISVGIcon::getSource( const int& size ) const { + static const DrawablePtr empty; + auto it = mSizes.find( size ); + if ( it != mSizes.end() ) + return it->second; Image::FormatConfiguration format; if ( mOriSize == Sizei::Zero ) { @@ -87,7 +98,7 @@ Drawable* UISVGIcon::getSize( const int& size ) const { mOriSize = { w, h }; mOriChannels = c; } else { - return nullptr; + return empty; } } format.svgScale( size / (Float)eemax( mOriSize.x, mOriSize.y ) ); @@ -95,8 +106,10 @@ Drawable* UISVGIcon::getSize( const int& size ) const { (const unsigned char*)&mSVGXml[0], mSVGXml.size(), false, Texture::ClampMode::ClampToEdge, false, false, format ); - mSVGs[size] = texture; - return texture.get(); + if ( !texture ) + return empty; + const_cast( this )->setSource( size, std::move( texture ) ); + return UIIcon::getSource( size ); } UISVGIcon::UISVGIcon( const std::string& name, const std::string& svgXML ) : diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 51924a47e..0d5b3b02d 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -66,11 +67,8 @@ UIImage* UIImage::NewWithTag( const std::string& tag ) { UIImage::UIImage( const std::string& tag ) : UIWidget( tag ), mScaleType( UIScaleType::None ), - mDrawable( NULL ), mColor(), mAlignOffset( 0, 0 ), - mResourceChangeCb( 0 ), - mDrawableOwner( false ), mAsyncImageAlive( std::make_shared>( true ) ) { mFlags |= UI_AUTO_SIZE; @@ -82,7 +80,7 @@ UIImage::UIImage() : UIImage( "image" ) {} UIImage::~UIImage() { if ( mAsyncImageAlive ) mAsyncImageAlive->store( false, std::memory_order_release ); - safeDeleteDrawable(); + clearDrawable(); } Uint32 UIImage::getType() const { @@ -93,16 +91,15 @@ bool UIImage::isType( const Uint32& type ) const { return UIImage::getType() == type ? true : UIWidget::isType( type ); } -UIImage* UIImage::setDrawable( Drawable* drawable, bool ownIt ) { +UIImage* UIImage::setDrawable( DrawablePtr drawable ) { if ( drawable == mDrawable ) return this; Sizef oldSize( mSize ); - safeDeleteDrawable(); + clearDrawable(); - mDrawable = drawable; - mDrawableOwner = ownIt; + mDrawable = std::move( drawable ); sendCommonEvent( Event::OnResourceChange ); if ( mDrawable ) { @@ -110,17 +107,15 @@ UIImage* UIImage::setDrawable( Drawable* drawable, bool ownIt ) { if ( !isSubscribedForScheduledUpdate() ) subscribeScheduledUpdate(); - mResourceChangeCb = - static_cast( mDrawable )->pushEventsCallback( [this]( auto, auto, auto ) { - invalidateDraw(); - } ); + mSpriteChangeCb = + static_cast( mDrawable.get() ) + ->pushEventsCallback( [this]( auto, auto, auto ) { invalidateDraw(); } ); } else { if ( mDrawable->isDrawableResource() ) { - mResourceChangeCb = - static_cast( mDrawable ) - ->pushResourceChangeCallback( [this]( auto, auto event, auto res ) { - onDrawableResourceEvent( event, res ); - } ); + mResourceChangeConnection = + static_cast( mDrawable.get() ) + ->connectResourceChange( + [this]( DrawableResource& ) { onDrawableResourceChange(); } ); } if ( isSubscribedForScheduledUpdate() ) @@ -141,10 +136,7 @@ UIImage* UIImage::setDrawable( Drawable* drawable, bool ownIt ) { } UIImage* UIImage::setDrawable( TexturePtr texture ) { - Texture* drawable = texture.get(); - setDrawable( drawable, false ); - mTexture = std::move( texture ); - return this; + return setDrawable( texture ? TextureDrawable::New( std::move( texture ) ) : DrawablePtr{} ); } void UIImage::onAutoSize() { @@ -278,7 +270,7 @@ void UIImage::setAlpha( const Float& alpha ) { mColor.a = (Uint8)alpha; } -Drawable* UIImage::getDrawable() const { +const DrawablePtr& UIImage::getDrawable() const { return mDrawable; } @@ -316,36 +308,25 @@ void UIImage::autoAlign() { } } -void UIImage::safeDeleteDrawable() { +void UIImage::clearDrawable() { if ( mDrawable && mDrawable->getDrawableType() == Drawable::SPRITE ) { - static_cast( mDrawable )->popEventsCallback( mResourceChangeCb ); - } else if ( mDrawable && mDrawable->isDrawableResource() ) { - static_cast( mDrawable )->popResourceChangeCallback( mResourceChangeCb ); - mResourceChangeCb = 0; + static_cast( mDrawable.get() )->popEventsCallback( mSpriteChangeCb ); + mSpriteChangeCb = 0; } - if ( mDrawable && mDrawableOwner ) { - eeSAFE_DELETE( mDrawable ); - - mDrawableOwner = false; - } - - mTexture.reset(); + mResourceChangeConnection.disconnect(); + mDrawable.reset(); } -void UIImage::onDrawableResourceEvent( DrawableResource::Event event, DrawableResource* ) { - if ( event == DrawableResource::Change ) { - runOnMainThread( [this] { - auto s = mSize; - onAutoSize(); - calcDestSize(); - if ( mSize != s ) - notifyLayoutAttrChangeParent( LayoutInvalidation::ParentReplacedFormatting ); - invalidateDraw(); - } ); - } else if ( event == DrawableResource::Unload ) { - mDrawable = NULL; - } +void UIImage::onDrawableResourceChange() { + runOnMainThread( [this] { + auto s = mSize; + onAutoSize(); + calcDestSize(); + if ( mSize != s ) + notifyLayoutAttrChangeParent( LayoutInvalidation::ParentReplacedFormatting ); + invalidateDraw(); + } ); } bool UIImage::loadFileDrawable( const Network::URI& uri ) { @@ -400,7 +381,11 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { std::string url = uri.toString(); ResourceScopePtr resourceScope = scene->getResourceScope(); if ( TexturePtr texture = resourceScope->findTexture( url ) ) { - if ( mDrawable != texture.get() ) { + TextureDrawable* textureDrawable = + mDrawable && mDrawable->getDrawableType() == Drawable::TEXTUREDRAWABLE + ? static_cast( mDrawable.get() ) + : nullptr; + if ( !textureDrawable || textureDrawable->getTexture() != texture ) { ++mRemoteImageLoadId; setDrawable( std::move( texture ) ); } @@ -436,7 +421,7 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { [alive, loadId, texture, imageData = std::move( imageData ), this]( UISceneNode* ) mutable { if ( !alive || !alive->load( std::memory_order_acquire ) || - loadId != mRemoteImageLoadId || mDrawable != texture.get() ) + loadId != mRemoteImageLoadId ) return; Image image( reinterpret_cast( imageData.data() ), @@ -515,7 +500,7 @@ std::string UIImage::getPropertyString( const PropertyDefinition* propertyDef, void UIImage::scheduledUpdate( const Time& time ) { if ( mDrawable && mDrawable->getDrawableType() == Drawable::SPRITE ) - static_cast( mDrawable )->update( time ); + static_cast( mDrawable.get() )->update( time ); } std::vector UIImage::getPropertiesImplemented() const { @@ -546,7 +531,6 @@ bool UIImage::applyProperty( const StyleSheetProperty& attribute ) { std::string path( attribute.getValue() ); URI uri( path ); - bool ownIt; UISceneNode* scene = getUISceneNode(); if ( scene && uri.getScheme().empty() && !scene->getURI().empty() ) { @@ -562,33 +546,28 @@ bool UIImage::applyProperty( const StyleSheetProperty& attribute ) { if ( mDeferLoad && uri.getScheme() == "file" && loadFileDrawable( uri ) ) break; - Drawable* createdDrawable = + DrawablePtr createdDrawable = StyleSheetSpecification::instance()->getDrawableImageParser().createDrawable( - path, mSize, ownIt, this ); + path, mSize, this ); if ( createdDrawable ) { - setDrawable( createdDrawable, ownIt ); + setDrawable( std::move( createdDrawable ) ); } else { - Drawable* res = NULL; - if ( NULL != ( res = DrawableSearcher::searchByName( - path, false, scene ? scene->getReferer() : URI(), - scene ? scene->getResourceScope().get() : nullptr ) ) ) - setDrawable( res, res->getDrawableType() == Drawable::SPRITE ); + setDrawable( DrawableSearcher::searchByName( + path, false, scene ? scene->getReferer() : URI(), + scene ? scene->getResourceScope().get() : nullptr ) ); } break; } case PropertyId::Icon: { std::string val = attribute.asString(); - Drawable* icon = NULL; - bool ownIt; UIIcon* iconF = getUISceneNode()->findIcon( val ); if ( iconF ) { setDrawable( - iconF->getSize( mSize.getHeight() - mPaddingPx.Top - mPadding.Bottom ) ); - } else if ( NULL != - ( icon = StyleSheetSpecification::instance() - ->getDrawableImageParser() - .createDrawable( val, getPixelsSize(), ownIt, this ) ) ) { - setDrawable( icon, ownIt ); + iconF->createDrawable( mSize.getHeight() - mPaddingPx.Top - mPadding.Bottom ) ); + } else if ( DrawablePtr icon = StyleSheetSpecification::instance() + ->getDrawableImageParser() + .createDrawable( val, getPixelsSize(), this ) ) { + setDrawable( std::move( icon ) ); } break; } diff --git a/src/eepp/ui/uimenu.cpp b/src/eepp/ui/uimenu.cpp index 469197ff0..0edc8ea03 100644 --- a/src/eepp/ui/uimenu.cpp +++ b/src/eepp/ui/uimenu.cpp @@ -58,20 +58,20 @@ void UIMenu::onPaddingChange() { widgetsSetPos(); } -UIMenuItem* UIMenu::createMenuItem( const String& text, Drawable* icon, +UIMenuItem* UIMenu::createMenuItem( const String& text, DrawablePtr icon, const String& shortcutText ) { UIMenuItem* widget = UIMenuItem::New(); widget->setHorizontalAlign( UI_HALIGN_LEFT ); widget->setParent( this ); widget->setIconMinimumSize( mIconMinSize ); - widget->setIcon( icon ); + widget->setIcon( std::move( icon ) ); widget->setText( text ); widget->setShortcutText( shortcutText ); return widget; } -UIMenuItem* UIMenu::add( const String& text, Drawable* icon, const String& shortcutText ) { - UIMenuItem* menuItem = createMenuItem( text, icon, shortcutText ); +UIMenuItem* UIMenu::add( const String& text, DrawablePtr icon, const String& shortcutText ) { + UIMenuItem* menuItem = createMenuItem( text, std::move( icon ), shortcutText ); add( menuItem ); return menuItem; } @@ -113,19 +113,19 @@ UIMenuRadioButton* UIMenu::addRadioButton( const String& text, const bool& activ return radioButton; } -UIMenuSubMenu* UIMenu::createSubMenu( const String& text, Drawable* icon, UIMenu* subMenu ) { +UIMenuSubMenu* UIMenu::createSubMenu( const String& text, DrawablePtr icon, UIMenu* subMenu ) { UIMenuSubMenu* menu = UIMenuSubMenu::New(); menu->setHorizontalAlign( UI_HALIGN_LEFT ); menu->setParent( this ); menu->setIconMinimumSize( mIconMinSize ); - menu->setIcon( icon ); + menu->setIcon( std::move( icon ) ); menu->setText( text ); menu->setSubMenu( subMenu ); return menu; } -UIMenuSubMenu* UIMenu::addSubMenu( const String& text, Drawable* icon, UIMenu* subMenu ) { - UIMenuSubMenu* menu = createSubMenu( text, icon, subMenu ); +UIMenuSubMenu* UIMenu::addSubMenu( const String& text, DrawablePtr icon, UIMenu* subMenu ) { + UIMenuSubMenu* menu = createSubMenu( text, std::move( icon ), subMenu ); add( menu ); return menu; } @@ -269,8 +269,8 @@ void UIMenu::removeAll() { resizeMe(); } -void UIMenu::insert( const String& text, Drawable* icon, const Uint32& index ) { - insert( createMenuItem( text, icon ), index ); +void UIMenu::insert( const String& text, DrawablePtr icon, const Uint32& index ) { + insert( createMenuItem( text, std::move( icon ) ), index ); } void UIMenu::insert( UIWidget* widget, const Uint32& index ) { @@ -553,13 +553,14 @@ Uint32 UIMenu::onKeyDown( const KeyEvent& event ) { return UIWidget::onKeyDown( event ); } -static Drawable* getIconDrawable( const std::string& name, UIIconThemeManager* iconThemeManager ) { - Drawable* iconDrawable = nullptr; +static DrawablePtr getIconDrawable( const std::string& name, + UIIconThemeManager* iconThemeManager ) { + DrawablePtr iconDrawable; if ( nullptr != iconThemeManager ) { UIIcon* icon = iconThemeManager->findIcon( name ); if ( icon ) { // TODO: Fix size - iconDrawable = icon->getSize( PixelDensity::dpToPx( 16 ) ); + iconDrawable = icon->createDrawable( PixelDensity::dpToPx( 16 ) ); } } if ( nullptr == iconDrawable ) diff --git a/src/eepp/ui/uimessagebox.cpp b/src/eepp/ui/uimessagebox.cpp index 444253374..7381b8f33 100644 --- a/src/eepp/ui/uimessagebox.cpp +++ b/src/eepp/ui/uimessagebox.cpp @@ -155,16 +155,17 @@ void UIMessageBox::setTheme( UITheme* theme ) { mButtonCancel->setTheme( theme ); if ( i18n( "msg_box_retry", "Retry" ) != mButtonOK->getText() ) { - Drawable* okIcon = getUISceneNode()->findIconDrawable( "ok", PixelDensity::dpToPxI( 16 ) ); - Drawable* cancelIcon = + DrawablePtr okIcon = + getUISceneNode()->findIconDrawable( "ok", PixelDensity::dpToPxI( 16 ) ); + DrawablePtr cancelIcon = getUISceneNode()->findIconDrawable( "cancel", PixelDensity::dpToPxI( 16 ) ); if ( NULL != okIcon ) { - mButtonOK->setIcon( okIcon ); + mButtonOK->setIcon( std::move( okIcon ) ); } if ( NULL != cancelIcon ) { - mButtonCancel->setIcon( cancelIcon ); + mButtonCancel->setIcon( std::move( cancelIcon ) ); } } diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index 997eb2e63..a8861f8cb 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -725,8 +725,8 @@ UINodeDrawable* UINode::setBackgroundFillEnabled( bool enabled ) { return mBackground; } -UINode* UINode::setBackgroundDrawable( Drawable* drawable, bool ownIt, int index ) { - setBackgroundFillEnabled( true )->setDrawable( index, drawable, ownIt ); +UINode* UINode::setBackgroundDrawable( DrawablePtr drawable, int index ) { + setBackgroundFillEnabled( true )->setDrawable( index, std::move( drawable ) ); return this; } @@ -838,8 +838,8 @@ UINodeDrawable* UINode::setForegroundFillEnabled( bool enabled ) { return mForeground; } -UINode* UINode::setForegroundDrawable( Drawable* drawable, bool ownIt, int index ) { - setForegroundFillEnabled( true )->setDrawable( index, drawable, ownIt ); +UINode* UINode::setForegroundDrawable( DrawablePtr drawable, int index ) { + setForegroundFillEnabled( true )->setDrawable( index, std::move( drawable ) ); return this; } @@ -1253,12 +1253,7 @@ UINode* UINode::setThemeSkin( UITheme* Theme, const std::string& skinName ) { UINode* UINode::setSkin( const UISkin& Skin ) { removeSkin(); - - writeNodeFlag( NODE_FLAG_SKIN_OWNER, 1 ); - - UISkin* SkinCopy = const_cast( &Skin )->clone(); - - mSkinState = UISkinState::New( SkinCopy ); + mSkinState = UISkinState::New( Skin.clone() ); onThemeLoaded(); @@ -1278,7 +1273,7 @@ UINode* UINode::setSkin( UISkin* skin ) { removeSkin(); - mSkinState = UISkinState::New( skin ); + mSkinState = UISkinState::New( skin->clone() ); mSkinState->setState( InitialState ); onThemeLoaded(); @@ -1302,12 +1297,6 @@ const Color& UINode::getSkinColor() const { } void UINode::removeSkin() { - if ( NULL != mSkinState && ( mNodeFlags & NODE_FLAG_SKIN_OWNER ) ) { - UISkin* tSkin = mSkinState->getSkin(); - - eeSAFE_DELETE( tSkin ); - } - eeSAFE_DELETE( mSkinState ); } diff --git a/src/eepp/ui/uinodedrawable.cpp b/src/eepp/ui/uinodedrawable.cpp index 467ccd1e6..e1271682e 100644 --- a/src/eepp/ui/uinodedrawable.cpp +++ b/src/eepp/ui/uinodedrawable.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -100,10 +101,6 @@ UINodeDrawable::~UINodeDrawable() { } void UINodeDrawable::clearDrawables() { - for ( auto& drawable : mGroup ) { - eeDelete( drawable.second ); - } - mGroup.clear(); mBackgroundColor.setColor( Color::Transparent ); } @@ -124,24 +121,24 @@ UINodeDrawable::LayerDrawable* UINodeDrawable::getLayer( int index ) { auto it = mGroup.find( index ); if ( it == mGroup.end() ) { - mGroup[index] = UINodeDrawable::LayerDrawable::New( this ); + mGroup[index] = LayerDrawablePtr( UINodeDrawable::LayerDrawable::New( this ) ); // HTML background-repeat defaults to "repeat", non-HTML to // "no-repeat". The LayerDrawable constructor uses NoRepeat // (the eepp/non-HTML default), so reset it for Html mode. if ( mBackgroundMode == BackgroundMode::Html ) { - auto* layer = mGroup[index]; + auto* layer = mGroup[index].get(); layer->setRepeatX( RepeatX::Repeat ); layer->setRepeatY( RepeatY::Repeat ); } } - return mGroup[index]; + return mGroup[index].get(); } -void UINodeDrawable::setDrawable( int index, Drawable* drawable, bool ownIt ) { +void UINodeDrawable::setDrawable( int index, DrawablePtr drawable ) { if ( drawable != getLayer( index )->getDrawable() ) { - getLayer( index )->setDrawable( drawable, ownIt ); + getLayer( index )->setDrawable( std::move( drawable ) ); } } @@ -245,7 +242,7 @@ void UINodeDrawable::setBackgroundMode( BackgroundMode mode ) { // still carry the LayerDrawable default (NoRepeat for both axes). if ( mode == BackgroundMode::Html ) { for ( auto& entry : mGroup ) { - auto* layer = entry.second; + auto* layer = entry.second.get(); if ( layer->getRepeatX() == RepeatX::NoRepeat && layer->getRepeatY() == RepeatY::NoRepeat ) { layer->setRepeatX( RepeatX::Repeat ); @@ -331,7 +328,7 @@ void UINodeDrawable::draw( const Vector2f& position, const Sizef& size, const Ui // "The background images are drawn on stacking context layers on top of each other. The first // layer specified is drawn as if it is closest to the user." for ( auto drawableIt = mGroup.rbegin(); drawableIt != mGroup.rend(); ++drawableIt ) { - UINodeDrawable::LayerDrawable* drawable = drawableIt->second; + UINodeDrawable::LayerDrawable* drawable = drawableIt->second.get(); bool clipContent = mBackgroundMode == BackgroundMode::Html && drawable->getClip() == LayerDrawable::Clip::ContentBox; @@ -411,9 +408,6 @@ UINodeDrawable::LayerDrawable::LayerDrawable( UINodeDrawable* container ) : mPositionY( "0px" ), mSizeEq( "auto" ), mNeedsUpdate( false ), - mOwnsDrawable( false ), - mDrawable( NULL ), - mResourceChangeCbId( 0 ), mRepeatX( RepeatX::NoRepeat ), mRepeatY( RepeatY::NoRepeat ), mOriginEq( "padding-box" ), @@ -428,13 +422,7 @@ UINodeDrawable::LayerDrawable::~LayerDrawable() { if ( mAsyncDrawableAlive ) mAsyncDrawableAlive->store( false, std::memory_order_release ); - if ( NULL != mDrawable && 0 != mResourceChangeCbId && mDrawable->isDrawableResource() ) { - reinterpret_cast( mDrawable ) - ->popResourceChangeCallback( mResourceChangeCbId ); - } - - if ( mOwnsDrawable ) - eeSAFE_DELETE( mDrawable ); + mResourceChangeConnection.disconnect(); } void UINodeDrawable::LayerDrawable::draw() { @@ -497,8 +485,8 @@ void UINodeDrawable::LayerDrawable::draw( const Vector2f& position, const Sizef& mDrawable->draw( Vector2f( xPos, effectivePos.y + mOffset.y ), tileSz ); break; case RepeatY::Repeat: - repeatYdraw( mDrawable, effectivePos, Vector2f( xPos - effectivePos.x, mOffset.y ), - mSize, tileSz ); + repeatYdraw( mDrawable.get(), effectivePos, + Vector2f( xPos - effectivePos.x, mOffset.y ), mSize, tileSz ); break; case RepeatY::Space: { if ( drawH <= 0 ) @@ -610,7 +598,7 @@ void UINodeDrawable::LayerDrawable::setSize( const Sizef& size ) { } } -Drawable* UINodeDrawable::LayerDrawable::getDrawable() const { +const DrawablePtr& UINodeDrawable::LayerDrawable::getDrawable() const { return mDrawable; } @@ -618,51 +606,30 @@ const std::string& UINodeDrawable::LayerDrawable::getDrawableRef() const { return mDrawableRef; } -void UINodeDrawable::LayerDrawable::setDrawable( Drawable* drawable, const bool& ownIt ) { +void UINodeDrawable::LayerDrawable::setDrawable( DrawablePtr drawable ) { if ( drawable == mDrawable ) return; - if ( NULL != mDrawable ) { - if ( mDrawable->isDrawableResource() ) { - reinterpret_cast( mDrawable ) - ->popResourceChangeCallback( mResourceChangeCbId ); - } + mResourceChangeConnection.disconnect(); - if ( mOwnsDrawable ) { - eeSAFE_DELETE( mDrawable ); - } - } - mTexture.reset(); - - mDrawable = drawable; + mDrawable = std::move( drawable ); mDrawableRef = ""; - mOwnsDrawable = ownIt; invalidate(); if ( NULL != mDrawable && mDrawable->isDrawableResource() ) { - mResourceChangeCbId = - reinterpret_cast( mDrawable ) - ->pushResourceChangeCallback( - [this]( Uint32, DrawableResource::Event event, DrawableResource* ) { - invalidate(); - if ( event == DrawableResource::Event::Unload ) { - mResourceChangeCbId = 0; - mDrawable = NULL; - mOwnsDrawable = false; - } - } ); + mResourceChangeConnection = + reinterpret_cast( mDrawable.get() ) + ->connectResourceChange( [this]( DrawableResource& ) { invalidate(); } ); } } void UINodeDrawable::LayerDrawable::setDrawable( TexturePtr texture ) { - Texture* drawable = texture.get(); - setDrawable( drawable, false ); - mTexture = std::move( texture ); + setDrawable( texture ? TextureDrawable::New( std::move( texture ) ) : DrawablePtr{} ); } void UINodeDrawable::LayerDrawable::setDrawable( const std::string& drawableRef ) { if ( drawableRef == "none" ) { - setDrawable( nullptr, false ); + setDrawable( DrawablePtr{} ); return; } @@ -671,10 +638,9 @@ void UINodeDrawable::LayerDrawable::setDrawable( const std::string& drawableRef return; } - bool ownIt; - Drawable* drawable = createDrawable( drawableRef, mSize, ownIt ); + DrawablePtr drawable = createDrawable( drawableRef, mSize ); - setDrawable( drawable, ownIt ); + setDrawable( std::move( drawable ) ); mDrawableRef = drawableRef; } @@ -714,7 +680,11 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value std::string url = uri.toString(); ResourceScopePtr resourceScope = scene->getResourceScope(); if ( TexturePtr texture = resourceScope->findTexture( url ) ) { - if ( mDrawable != texture.get() ) { + TextureDrawable* textureDrawable = + mDrawable && mDrawable->getDrawableType() == Drawable::TEXTUREDRAWABLE + ? static_cast( mDrawable.get() ) + : nullptr; + if ( !textureDrawable || textureDrawable->getTexture() != texture ) { ++mRemoteDrawableLoadId; setDrawable( std::move( texture ) ); } @@ -750,7 +720,7 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value [alive, loadId, texture, imageData = std::move( imageData ), this]( UISceneNode* ) mutable { if ( !alive || !alive->load( std::memory_order_acquire ) || - loadId != mRemoteDrawableLoadId || mDrawable != texture.get() ) + loadId != mRemoteDrawableLoadId ) return; Image image( reinterpret_cast( imageData.data() ), @@ -769,10 +739,10 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value return true; } -Drawable* UINodeDrawable::LayerDrawable::createDrawable( const std::string& value, - const Sizef& size, bool& ownIt ) { +DrawablePtr UINodeDrawable::LayerDrawable::createDrawable( const std::string& value, + const Sizef& size ) { return CSS::StyleSheetSpecification::instance()->getDrawableImageParser().createDrawable( - value, size, ownIt, mContainer->getOwner() ); + value, size, mContainer->getOwner() ); } const Vector2f& UINodeDrawable::LayerDrawable::getOffset() const { diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index e65346603..fd913a2c5 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -397,13 +397,13 @@ void UIPushButton::updateTextBox() { } } -UIPushButton* UIPushButton::setIcon( Drawable* icon, bool ownIt ) { +UIPushButton* UIPushButton::setIcon( DrawablePtr icon ) { if ( nullptr == mIcon || mIcon->getDrawable() != icon ) { if ( icon ) getIcon()->setPixelsSize( icon->getPixelsSize() ); if ( icon == nullptr && mIcon == nullptr ) return this; - getIcon()->setDrawable( icon, ownIt ); + getIcon()->setDrawable( std::move( icon ) ); updateTextBox(); } return this; @@ -681,18 +681,15 @@ bool UIPushButton::applyProperty( const StyleSheetProperty& attribute ) { break; case PropertyId::Icon: { const std::string& val = attribute.value(); - Drawable* icon = NULL; - bool ownIt; UIIcon* iconF = getUISceneNode()->findIcon( val ); if ( iconF ) { - setIcon( iconF->getSize( + setIcon( iconF->createDrawable( eemax( mSize.getHeight() - mPaddingPx.Top - mPadding.Bottom, - PixelDensity::dpToPxI( 16 ) ) ) ); - } else if ( NULL != - ( icon = StyleSheetSpecification::instance() - ->getDrawableImageParser() - .createDrawable( val, getPixelsSize(), ownIt, this ) ) ) { - setIcon( icon, ownIt ); + PixelDensity::dpToPxI( 16 ) ) ) ); + } else if ( DrawablePtr icon = StyleSheetSpecification::instance() + ->getDrawableImageParser() + .createDrawable( val, getPixelsSize(), this ) ) { + setIcon( std::move( icon ) ); } break; } diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 3831d3f04..450d4e939 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -1347,11 +1347,10 @@ UIIcon* UISceneNode::findIcon( const std::string& iconName ) { return getUIIconThemeManager()->findIcon( iconName ); } -Drawable* UISceneNode::findIconDrawable( const std::string& iconName, const size_t& drawableSize ) { +DrawablePtr UISceneNode::findIconDrawable( const std::string& iconName, + const size_t& drawableSize ) { UIIcon* icon = findIcon( iconName ); - if ( icon ) - return icon->getSize( drawableSize ); - return nullptr; + return icon ? icon->createDrawable( drawableSize ) : DrawablePtr{}; } CSS::MediaFeatures UISceneNode::getMediaFeatures() const { diff --git a/src/eepp/ui/uiskin.cpp b/src/eepp/ui/uiskin.cpp index efc498bdf..9d144db82 100644 --- a/src/eepp/ui/uiskin.cpp +++ b/src/eepp/ui/uiskin.cpp @@ -31,23 +31,31 @@ Sizef UISkin::getPixelsSize( const Uint32& state ) { return StateListDrawable::getPixelsSize( state ); } -UISkin* UISkin::clone( const std::string& NewName ) { - UISkin* SkinS = UISkin::New( NewName ); - - SkinS->mColor = mColor; - SkinS->mPosition = mPosition; - SkinS->mDrawables = mDrawables; - SkinS->mCurrentState = mCurrentState; - SkinS->mCurrentDrawable = mCurrentDrawable; - SkinS->mDrawablesOwnership = mDrawablesOwnership; - - return SkinS; +ResourcePtr UISkin::clone( const std::string& newName ) const { + auto skin = ResourcePtr( eeNew( UISkin, ( newName ) ), ResourceDeleter() ); + skin->setColor( mColor ); + skin->setPosition( mPosition ); + for ( const auto& state : mDrawables ) { + if ( !state.second ) + continue; + DrawablePtr drawable = state.second->createInstance(); + if ( !drawable ) + return {}; + skin->setStateDrawable( state.first, std::move( drawable ) ); + } + skin->mDrawableColors = mDrawableColors; + skin->setState( mCurrentState ); + return skin; } -UISkin* UISkin::clone() { +ResourcePtr UISkin::clone() const { return clone( mName ); } +DrawablePtr UISkin::createInstance() const { + return clone(); +} + Rectf UISkin::getBorderSize() { return getBorderSize( UIState::StateFlagNormal ); } @@ -55,7 +63,7 @@ Rectf UISkin::getBorderSize() { Rectf UISkin::getBorderSize( const Uint32& state ) { if ( hasDrawableState( state ) && mDrawables[state]->getDrawableType() == EE::Graphics::Drawable::Type::NINEPATCH ) { - NinePatch* ninePatch( static_cast( mDrawables[state] ) ); + NinePatch* ninePatch( static_cast( mDrawables[state].get() ) ); TextureRegion* stl( ninePatch->getTextureRegion( NinePatch::Left ) ); TextureRegion* str( ninePatch->getTextureRegion( NinePatch::Right ) ); TextureRegion* stt( ninePatch->getTextureRegion( NinePatch::Up ) ); diff --git a/src/eepp/ui/uiskinstate.cpp b/src/eepp/ui/uiskinstate.cpp index ab7a3164e..a81fcd21e 100644 --- a/src/eepp/ui/uiskinstate.cpp +++ b/src/eepp/ui/uiskinstate.cpp @@ -3,18 +3,19 @@ namespace EE { namespace UI { -UISkinState* UISkinState::New( UISkin* skin ) { - return eeNew( UISkinState, ( skin ) ); +UISkinState* UISkinState::New( ResourcePtr skin ) { + return eeNew( UISkinState, ( std::move( skin ) ) ); } -UISkinState::UISkinState( UISkin* Skin ) : mSkin( Skin ), mCurrentColor( Color::White ) { +UISkinState::UISkinState( ResourcePtr skin ) : + mSkin( std::move( skin ) ), mCurrentColor( Color::White ) { eeASSERT( NULL != mSkin ); } UISkinState::~UISkinState() {} UISkin* UISkinState::getSkin() const { - return mSkin; + return mSkin.get(); } bool UISkinState::stateExists( const Uint32& State ) const { diff --git a/src/eepp/ui/uisprite.cpp b/src/eepp/ui/uisprite.cpp index 91f34e117..5eb1eb4d5 100644 --- a/src/eepp/ui/uisprite.cpp +++ b/src/eepp/ui/uisprite.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -14,17 +15,13 @@ UISprite* UISprite::New() { UISprite::UISprite() : UIWidget( "sprite" ), - mSprite( NULL ), mRender( RENDER_NORMAL ), mAlignOffset( 0, 0 ), mTextureRegionLast( NULL ) { subscribeScheduledUpdate(); } -UISprite::~UISprite() { - if ( deallocSprite() ) - eeSAFE_DELETE( mSprite ); -} +UISprite::~UISprite() {} Uint32 UISprite::getType() const { return UI_TYPE_SPRITE; @@ -34,16 +31,10 @@ bool UISprite::isType( const Uint32& type ) const { return UISprite::getType() == type ? true : UIWidget::isType( type ); } -Uint32 UISprite::deallocSprite() { - return mNodeFlags & NODE_FLAG_FREE_USE; -} - -UISprite* UISprite::setSprite( Graphics::Sprite* sprite ) { - if ( deallocSprite() ) - eeSAFE_DELETE( mSprite ); - - mSprite = sprite; - mSprite->setAutoAnimate( false ); +UISprite* UISprite::setSprite( Graphics::SpritePtr sprite ) { + mSprite = std::move( sprite ); + if ( mSprite ) + mSprite->setAutoAnimate( false ); updateSize(); return this; @@ -103,11 +94,7 @@ void UISprite::setAlpha( const Float& alpha ) { UIWidget::setAlpha( alpha ); } -Graphics::Sprite* UISprite::getSprite() const { - return mSprite; -} - -Drawable* UISprite::getDrawable() const { +const Graphics::SpritePtr& UISprite::getSprite() const { return mSprite; } @@ -189,15 +176,6 @@ const Vector2f& UISprite::getAlignOffset() const { return mAlignOffset; } -UISprite* UISprite::setIsSpriteOwner( const bool& dealloc ) { - writeNodeFlag( NODE_FLAG_FREE_USE, dealloc ? 1 : 0 ); - return this; -} - -bool UISprite::getDeallocSprite() { - return 0 != ( mNodeFlags & NODE_FLAG_FREE_USE ); -} - void UISprite::onSizeChange() { autoAlign(); notifyLayoutAttrChange( LayoutInvalidation::Self ); @@ -238,7 +216,7 @@ bool UISprite::applyProperty( const StyleSheetProperty& attribute ) { path = func.getParameters().at( 0 ); } - Drawable* res = NULL; + DrawablePtr res; UISceneNode* scene = getUISceneNode(); if ( scene ) res = DrawableSearcher::searchByName( path, true, scene->getReferer(), @@ -246,15 +224,30 @@ bool UISprite::applyProperty( const StyleSheetProperty& attribute ) { else res = DrawableSearcher::searchByName( path, true ); - if ( NULL != res ) { - setIsSpriteOwner( true ); - - if ( res->getDrawableType() == Drawable::SPRITE ) { - setSprite( static_cast( res ) ); - } else if ( res->getDrawableType() == Drawable::TEXTUREREGION ) { - setSprite( Sprite::New( static_cast( res ) ) ); - } else if ( res->getDrawableType() == Drawable::TEXTURE ) { - setSprite( Sprite::New( static_cast( res )->getTextureId() ) ); + if ( res ) { + switch ( res->getDrawableType() ) { + case Drawable::SPRITE: + setSprite( std::static_pointer_cast( std::move( res ) ) ); + break; + case Drawable::TEXTUREREGION: + setSprite( + Sprite::New( std::static_pointer_cast( res ).get() ) ); + break; + case Drawable::TEXTUREDRAWABLE: { + auto sprite = Sprite::New(); + sprite->createStatic( + std::static_pointer_cast( res )->getTexture() ); + setSprite( std::move( sprite ) ); + break; + } + case Drawable::TEXTURE: { + auto sprite = Sprite::New(); + sprite->createStatic( std::static_pointer_cast( res ) ); + setSprite( std::move( sprite ) ); + break; + } + default: + break; } } break; diff --git a/src/eepp/ui/uisvg.cpp b/src/eepp/ui/uisvg.cpp index 03edcfafa..64f6eef2b 100644 --- a/src/eepp/ui/uisvg.cpp +++ b/src/eepp/ui/uisvg.cpp @@ -21,24 +21,6 @@ class XmlStringWriter : public pugi::xml_writer { } }; -class PendingSpriteTransfer { - public: - explicit PendingSpriteTransfer( Sprite* sprite ) : mSprite( sprite ) {} - PendingSpriteTransfer( const PendingSpriteTransfer& ) = delete; - PendingSpriteTransfer& operator=( const PendingSpriteTransfer& ) = delete; - - ~PendingSpriteTransfer() { eeSAFE_DELETE( mSprite ); } - - Sprite* release() { - Sprite* sprite = mSprite; - mSprite = nullptr; - return sprite; - } - - private: - Sprite* mSprite{ nullptr }; -}; - } // namespace UISvg* UISvg::New() { @@ -104,17 +86,12 @@ void UISvg::rasterizeSvg( const std::string& svgXml ) { if ( !texture ) return; - Sprite* sprite = Sprite::New(); + SpritePtr sprite = Sprite::New(); sprite->createStatic( std::move( texture ) ); - sprite->setAsTextureRegionOwner( true ); - auto spriteTransfer = std::make_shared( sprite ); - - runOnMainThread( [this, spriteTransfer] { - Sprite* sprite = spriteTransfer->release(); - if ( sprite ) { - setDrawable( sprite, true ); - } + runOnMainThread( [this, sprite = std::move( sprite )]() mutable { + if ( sprite ) + setDrawable( std::move( sprite ) ); } ); } diff --git a/src/eepp/ui/uitabwidget.cpp b/src/eepp/ui/uitabwidget.cpp index 128d2c3d8..35a4745c4 100644 --- a/src/eepp/ui/uitabwidget.cpp +++ b/src/eepp/ui/uitabwidget.cpp @@ -525,11 +525,11 @@ void UITabWidget::updateTabs() { } } -UITab* UITabWidget::createTab( const String& text, UINode* nodeOwned, Drawable* icon ) { +UITab* UITabWidget::createTab( const String& text, UINode* nodeOwned, DrawablePtr icon ) { UITab* tab = UITab::New(); tab->setParent( mTabBar ); tab->setFlags( UI_VALIGN_CENTER | UI_HALIGN_CENTER | UI_AUTO_SIZE ); - tab->setIcon( icon ); + tab->setIcon( std::move( icon ) ); tab->setText( text ); tab->setVisible( true ); tab->setEnabled( true ); @@ -553,8 +553,8 @@ UITab* UITabWidget::createTab( const String& text, UINode* nodeOwned, Drawable* return tab; } -UITab* UITabWidget::add( const String& text, UINode* nodeOwned, Drawable* icon ) { - UITab* tab = createTab( text, nodeOwned, icon ); +UITab* UITabWidget::add( const String& text, UINode* nodeOwned, DrawablePtr icon ) { + UITab* tab = createTab( text, nodeOwned, std::move( icon ) ); add( tab ); return tab; } @@ -789,9 +789,9 @@ void UITabWidget::removeAllTabs( bool destroyOwnedNode, bool immediateClose ) { } } -void UITabWidget::insertTab( const String& text, UINode* nodeOwned, Drawable* icon, +void UITabWidget::insertTab( const String& text, UINode* nodeOwned, DrawablePtr icon, const Uint32& index ) { - insertTab( createTab( text, nodeOwned, icon ), index ); + insertTab( createTab( text, nodeOwned, std::move( icon ) ), index ); } void UITabWidget::insertTab( UITab* Tab, const Uint32& index ) { diff --git a/src/eepp/ui/uitextinput.cpp b/src/eepp/ui/uitextinput.cpp index 44b09a486..8d103c349 100644 --- a/src/eepp/ui/uitextinput.cpp +++ b/src/eepp/ui/uitextinput.cpp @@ -979,11 +979,9 @@ void UITextInput::setEscapePastedText( bool escapePastedText ) { mEscapePastedText = escapePastedText; } -Drawable* UITextInput::findIcon( const std::string& name ) { +DrawablePtr UITextInput::findIcon( const std::string& name ) { UIIcon* icon = getUISceneNode()->findIcon( name ); - if ( icon ) - return icon->getSize( mMenuIconSize ); - return nullptr; + return icon ? icon->createDrawable( mMenuIconSize ) : DrawablePtr{}; } UIMenuItem* UITextInput::menuAdd( UIPopUpMenu* menu, const String& translateString, diff --git a/src/eepp/ui/uitextureregion.cpp b/src/eepp/ui/uitextureregion.cpp index aa4b481f6..0d86876bc 100644 --- a/src/eepp/ui/uitextureregion.cpp +++ b/src/eepp/ui/uitextureregion.cpp @@ -75,21 +75,15 @@ void UITextureRegion::draw() { UINode::draw(); if ( NULL != mTextureRegion && 0.f != mAlpha ) { - Sizef oDestSize = mTextureRegion->getDestSize(); - Vector2i oOff = mTextureRegion->getOffset(); + Sizef destSize = mTextureRegion->getDestSize(); + Vector2i offset = mTextureRegion->getOffset(); if ( mScaleType == UIScaleType::Expand ) { - mTextureRegion->setOffset( Vector2i( 0, 0 ) ); - mTextureRegion->setDestSize( - Vector2f( (int)mSize.x - mPaddingPx.Left - mPaddingPx.Right, - (int)mSize.y - mPaddingPx.Top - mPaddingPx.Bottom ) ); - - autoAlign(); - - drawTextureRegion(); - + offset = Vector2i::Zero; + destSize = Vector2f( (int)mSize.x - mPaddingPx.Left - mPaddingPx.Right, + (int)mSize.y - mPaddingPx.Top - mPaddingPx.Bottom ); } else if ( mScaleType == UIScaleType::FitInside ) { - mTextureRegion->setOffset( Vector2i( 0, 0 ) ); + offset = Vector2i::Zero; Sizef pxSize = mTextureRegion->getPixelsSize(); Float Scale1 = ( mSize.x - mPaddingPx.Left - mPaddingPx.Right ) / (Float)pxSize.x; @@ -99,44 +93,33 @@ void UITextureRegion::draw() { if ( Scale2 < Scale1 ) Scale1 = Scale2; - Sizef dst( pxSize.x * Scale1, pxSize.y * Scale1 ); - mTextureRegion->setDestSize( dst.floor() ); - - autoAlign(); - - drawTextureRegion(); + destSize = Sizef( pxSize.x * Scale1, pxSize.y * Scale1 ).floor(); } else { - mTextureRegion->setDestSize( Vector2f( (Float)pxSize.x, (Float)pxSize.y ) ); - - autoAlign(); - - drawTextureRegion(); + destSize = Vector2f( (Float)pxSize.x, (Float)pxSize.y ); } } else { - mTextureRegion->setOffset( - Vector2i( (Int32)( (Float)oOff.x / mTextureRegion->getPixelDensity() * - PixelDensity::getPixelDensity() ), - (Int32)( (Float)oOff.y / mTextureRegion->getPixelDensity() * - PixelDensity::getPixelDensity() ) ) ); - - mTextureRegion->setDestSize( Vector2f( (Float)mTextureRegion->getPixelsSize().x, - (Float)mTextureRegion->getPixelsSize().y ) ); - - autoAlign(); - - drawTextureRegion(); + offset = Vector2i( (Int32)( (Float)offset.x / mTextureRegion->getPixelDensity() * + PixelDensity::getPixelDensity() ), + (Int32)( (Float)offset.y / mTextureRegion->getPixelDensity() * + PixelDensity::getPixelDensity() ) ); + destSize = Vector2f( (Float)mTextureRegion->getPixelsSize().x, + (Float)mTextureRegion->getPixelsSize().y ); } - mTextureRegion->setDestSize( oDestSize ); - mTextureRegion->setOffset( oOff ); + autoAlign( destSize ); + drawTextureRegion( destSize, offset ); } } } -void UITextureRegion::drawTextureRegion() { - mTextureRegion->draw( std::trunc( mScreenPos.x ) + (int)mAlignOffset.x, - std::trunc( mScreenPos.y ) + (int)mAlignOffset.y, mColor, 0.f, - Vector2f::One, getBlendMode(), mRender ); +void UITextureRegion::drawTextureRegion( const Sizef& destSize, const Vector2i& offset ) { + const TexturePtr& texture = mTextureRegion->getTexture(); + if ( texture ) + texture->drawEx( std::trunc( mScreenPos.x ) + (int)mAlignOffset.x + offset.x, + std::trunc( mScreenPos.y ) + (int)mAlignOffset.y + offset.y, destSize.x, + destSize.y, 0.f, Vector2f::One, mColor, mColor, mColor, mColor, + getBlendMode(), mRender, OriginPoint( OriginPoint::OriginCenter ), + mTextureRegion->getSrcRect() ); } void UITextureRegion::setAlpha( const Float& alpha ) { @@ -169,19 +152,22 @@ void UITextureRegion::setRenderMode( const RenderMode& render ) { void UITextureRegion::autoAlign() { if ( NULL == mTextureRegion ) return; + autoAlign( mTextureRegion->getDestSize() ); +} +void UITextureRegion::autoAlign( const Sizef& drawableSize ) { if ( Font::getHorizontalAlign( mFlags ) == UI_HALIGN_CENTER ) { - mAlignOffset.x = ( mSize.getWidth() - mTextureRegion->getDestSize().x ) / 2; + mAlignOffset.x = ( mSize.getWidth() - drawableSize.x ) / 2; } else if ( Font::getHorizontalAlign( mFlags ) == UI_HALIGN_RIGHT ) { - mAlignOffset.x = mSize.getWidth() - mTextureRegion->getDestSize().x - mPaddingPx.Right; + mAlignOffset.x = mSize.getWidth() - drawableSize.x - mPaddingPx.Right; } else { mAlignOffset.x = mPaddingPx.Left; } if ( Font::getVerticalAlign( mFlags ) == UI_VALIGN_CENTER ) { - mAlignOffset.y = ( mSize.getHeight() - mTextureRegion->getDestSize().y ) / 2; + mAlignOffset.y = ( mSize.getHeight() - drawableSize.y ) / 2; } else if ( Font::getVerticalAlign( mFlags ) == UI_VALIGN_BOTTOM ) { - mAlignOffset.y = mSize.getHeight() - mTextureRegion->getDestSize().y - mPaddingPx.Bottom; + mAlignOffset.y = mSize.getHeight() - drawableSize.y - mPaddingPx.Bottom; } else { mAlignOffset.y = mPaddingPx.Top; } diff --git a/src/eepp/ui/uitheme.cpp b/src/eepp/ui/uitheme.cpp index 5ae328da0..a20bb6e9d 100644 --- a/src/eepp/ui/uitheme.cpp +++ b/src/eepp/ui/uitheme.cpp @@ -100,7 +100,8 @@ UITheme* UITheme::loadFromTextureAtlas( UITheme* tTheme, Graphics::TextureAtlas* if ( String::startsWith( name, sAbbrIcon ) ) { auto* icon = UIIcon::New( name.substr( sAbbrIcon.size() ) ); - icon->setSize( textureRegion->getPixelsSize().getWidth(), textureRegion ); + icon->setSource( textureRegion->getPixelsSize().getWidth(), + textureRegion->createInstance() ); tTheme->getIconTheme()->add( icon ); } else if ( String::startsWith( name, sAbbr ) ) { std::vector dotParts = String::split( name, '.' ); @@ -140,7 +141,7 @@ UITheme* UITheme::loadFromTextureAtlas( UITheme* tTheme, Graphics::TextureAtlas* int stateNum = UIState::getStateNumber( nameParts[nameParts.size() - 1] ); if ( -1 != stateNum ) - skins[skinName]->setStateDrawable( stateNum, drawable ); + skins[skinName]->setStateDrawable( stateNum, drawable->createInstance() ); } else { std::vector nameParts = String::split( name, '_' ); @@ -155,7 +156,8 @@ UITheme* UITheme::loadFromTextureAtlas( UITheme* tTheme, Graphics::TextureAtlas* skins[skinName] = tTheme->add( UISkin::New( skinName ) ); if ( -1 != stateNum ) - skins[skinName]->setStateDrawable( stateNum, textureRegion ); + skins[skinName]->setStateDrawable( stateNum, + textureRegion->createInstance() ); } } } @@ -199,7 +201,7 @@ UITheme* UITheme::loadFromDirectory( UITheme* tTheme, const std::string& Path, TextureRegion::New( TextureFactory::instance()->loadFromFile( fpath ), name ); tSG->add( drawable ); auto* icon = UIIcon::New( name.substr( sAbbrIcon.size() ) ); - icon->setSize( drawable->getPixelsSize().getWidth(), drawable ); + icon->setSource( drawable->getPixelsSize().getWidth(), drawable->createInstance() ); tTheme->getIconTheme()->add( icon ); } else if ( String::startsWith( name, sAbbr ) ) { std::vector dotParts = String::split( name, '.' ); @@ -240,7 +242,7 @@ UITheme* UITheme::loadFromDirectory( UITheme* tTheme, const std::string& Path, int stateNum = UIState::getStateNumber( nameParts[nameParts.size() - 1] ); if ( -1 != stateNum ) - skins[skinName]->setStateDrawable( stateNum, drawable ); + skins[skinName]->setStateDrawable( stateNum, drawable->createInstance() ); } else { std::vector nameParts = String::split( name, '_' ); @@ -258,7 +260,8 @@ UITheme* UITheme::loadFromDirectory( UITheme* tTheme, const std::string& Path, skins[skinName] = tTheme->add( UISkin::New( skinName ) ); if ( -1 != stateNum ) - skins[skinName]->setStateDrawable( stateNum, textureRegion ); + skins[skinName]->setStateDrawable( + stateNum, textureRegion->createInstance() ); } } } diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 483a0a9d1..5d6fcb467 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -289,8 +289,8 @@ UIWidget* UITreeView::updateCell( const Vector2& posIndex, const ModelInd if ( !mExpandersAsIcons && mExpandIcon && mContractIcon ) { minIndent = eemax( - mExpandIcon->getSize( mExpanderIconSize )->getPixelsSize().getWidth(), - mContractIcon->getSize( mExpanderIconSize )->getPixelsSize().getWidth() ) + + mExpandIcon->getSource( mExpanderIconSize )->getPixelsSize().getWidth(), + mContractIcon->getSource( mExpanderIconSize )->getPixelsSize().getWidth() ) + image->getLayoutPixelsMargin().Right; } @@ -300,14 +300,14 @@ UIWidget* UITreeView::updateCell( const Vector2& posIndex, const ModelInd if ( hasChildren ) { UIIcon* icon = getIndexMetadata( index ).open ? mExpandIcon : mContractIcon; - Drawable* drawable = icon ? icon->getSize( mExpanderIconSize ) : nullptr; + DrawablePtr drawable = icon ? icon->createDrawable( mExpanderIconSize ) : DrawablePtr{}; if ( drawable == nullptr ) { image->setVisible( false ); } else { image->setVisible( true ); image->setPixelsSize( drawable ? drawable->getPixelsSize() : Sizef( 0, 0 ) ); - image->setDrawable( drawable ); + image->setDrawable( std::move( drawable ) ); if ( !mExpandersAsIcons ) indentation = indentation - image->getPixelsSize().getWidth(); } @@ -330,7 +330,7 @@ UIWidget* UITreeView::updateCell( const Vector2& posIndex, const ModelInd cell->setIcon( icon.asDrawable() ); } else if ( icon.is( Variant::Type::Icon ) && icon.asIcon() ) { isVisible = true; - cell->setIcon( icon.asIcon()->getSize( mIconSize ) ); + cell->setIcon( icon.asIcon()->createDrawable( mIconSize ) ); } if ( cell->hasIcon() ) cell->getIcon()->setVisible( isVisible ); diff --git a/src/modules/eterm/include/eterm/ui/uiterminal.hpp b/src/modules/eterm/include/eterm/ui/uiterminal.hpp index 697d5e7ee..8125bcbc7 100644 --- a/src/modules/eterm/include/eterm/ui/uiterminal.hpp +++ b/src/modules/eterm/include/eterm/ui/uiterminal.hpp @@ -171,7 +171,7 @@ class UITerminal : public UIWidget { virtual bool onCreateContextMenu( const Vector2i& position, const Uint32& flags ); - Drawable* findIcon( const std::string& name ); + DrawablePtr findIcon( const std::string& name ); void createDefaultContextMenuOptions( UIPopUpMenu* menu ); diff --git a/src/modules/eterm/src/eterm/ui/uiterminal.cpp b/src/modules/eterm/src/eterm/ui/uiterminal.cpp index b44cdbd30..1346001c0 100644 --- a/src/modules/eterm/src/eterm/ui/uiterminal.cpp +++ b/src/modules/eterm/src/eterm/ui/uiterminal.cpp @@ -561,11 +561,9 @@ void UITerminal::createDefaultContextMenuOptions( UIPopUpMenu* menu ) { ->setEnabled( !getUISceneNode()->getWindow()->getClipboard()->getText().empty() ); } -Drawable* UITerminal::findIcon( const std::string& name ) { +DrawablePtr UITerminal::findIcon( const std::string& name ) { UIIcon* icon = getUISceneNode()->findIcon( name ); - if ( icon ) - return icon->getSize( mMenuIconSize ); - return nullptr; + return icon ? icon->createDrawable( mMenuIconSize ) : DrawablePtr{}; } UIMenuItem* UITerminal::menuAdd( UIPopUpMenu* menu, const String& translateString, diff --git a/src/modules/maps/include/eepp/maps/gameobjectsprite.hpp b/src/modules/maps/include/eepp/maps/gameobjectsprite.hpp index c906cc55f..376d34339 100644 --- a/src/modules/maps/include/eepp/maps/gameobjectsprite.hpp +++ b/src/modules/maps/include/eepp/maps/gameobjectsprite.hpp @@ -3,16 +3,13 @@ #include #include - -namespace EE { namespace Graphics { -class Sprite; -}} // namespace EE::Graphics +#include namespace EE { namespace Maps { class EE_MAPS_API GameObjectSprite : public GameObject { public: - GameObjectSprite( const Uint32& Flags, MapLayer* Layer, Graphics::Sprite* Sprite = NULL ); + GameObjectSprite( const Uint32& Flags, MapLayer* Layer, Graphics::SpritePtr sprite = {} ); virtual ~GameObjectSprite(); @@ -30,9 +27,9 @@ class EE_MAPS_API GameObjectSprite : public GameObject { virtual Sizei getSize(); - Graphics::Sprite* getSprite() const; + const Graphics::SpritePtr& getSprite() const; - void setSprite( Graphics::Sprite* sprite ); + void setSprite( Graphics::SpritePtr sprite ); virtual Uint32 getType() const; @@ -45,7 +42,7 @@ class EE_MAPS_API GameObjectSprite : public GameObject { virtual void setDataId( Uint32 Id ); protected: - Graphics::Sprite* mSprite; + Graphics::SpritePtr mSprite; Vector2i mTilePos; }; diff --git a/src/modules/maps/include/eepp/maps/gameobjecttextureregion.hpp b/src/modules/maps/include/eepp/maps/gameobjecttextureregion.hpp index 760d040b2..b000a62b8 100644 --- a/src/modules/maps/include/eepp/maps/gameobjecttextureregion.hpp +++ b/src/modules/maps/include/eepp/maps/gameobjecttextureregion.hpp @@ -42,7 +42,7 @@ class EE_MAPS_API GameObjectTextureRegion : public GameObject { virtual void setDataId( Uint32 Id ); protected: - Graphics::TextureRegion* mTextureRegion; + TextureRegionPtr mTextureRegion; Vector2f mPos; Vector2i mTilePos; }; diff --git a/src/modules/maps/include/eepp/maps/gameobjectvirtual.hpp b/src/modules/maps/include/eepp/maps/gameobjectvirtual.hpp index 61ee73a85..5bbb9f4f3 100644 --- a/src/modules/maps/include/eepp/maps/gameobjectvirtual.hpp +++ b/src/modules/maps/include/eepp/maps/gameobjectvirtual.hpp @@ -49,7 +49,7 @@ class EE_MAPS_API GameObjectVirtual : public GameObject { Uint32 mDataId; Vector2f mPos; MapLayer* mLayer; - TextureRegion* mTextureRegion; + TextureRegionPtr mTextureRegion; }; }} // namespace EE::Maps diff --git a/src/modules/maps/src/eepp/maps/gameobjectsprite.cpp b/src/modules/maps/src/eepp/maps/gameobjectsprite.cpp index e31a6ae25..13b4cca08 100644 --- a/src/modules/maps/src/eepp/maps/gameobjectsprite.cpp +++ b/src/modules/maps/src/eepp/maps/gameobjectsprite.cpp @@ -7,8 +7,8 @@ namespace EE { namespace Maps { GameObjectSprite::GameObjectSprite( const Uint32& Flags, MapLayer* Layer, - Graphics::Sprite* Sprite ) : - GameObject( Flags, Layer ), mSprite( Sprite ) { + Graphics::SpritePtr sprite ) : + GameObject( Flags, Layer ), mSprite( std::move( sprite ) ) { if ( NULL != mSprite ) { mSprite->setRenderMode( getRenderModeFromFlags() ); mSprite->setBlendMode( getBlendModeFromFlags() ); @@ -17,9 +17,7 @@ GameObjectSprite::GameObjectSprite( const Uint32& Flags, MapLayer* Layer, assignTilePos(); } -GameObjectSprite::~GameObjectSprite() { - eeSAFE_DELETE( mSprite ); -} +GameObjectSprite::~GameObjectSprite() {} Uint32 GameObjectSprite::getType() const { return GAMEOBJECT_TYPE_SPRITE; @@ -109,13 +107,12 @@ Sizei GameObjectSprite::getSize() { return Sizei(); } -Graphics::Sprite* GameObjectSprite::getSprite() const { +const Graphics::SpritePtr& GameObjectSprite::getSprite() const { return mSprite; } -void GameObjectSprite::setSprite( Graphics::Sprite* sprite ) { - eeSAFE_DELETE( mSprite ); - mSprite = sprite; +void GameObjectSprite::setSprite( Graphics::SpritePtr sprite ) { + mSprite = std::move( sprite ); mSprite->setRenderMode( getRenderModeFromFlags() ); mSprite->setBlendMode( getBlendModeFromFlags() ); mSprite->setAutoAnimate( false ); @@ -134,7 +131,7 @@ Uint32 GameObjectSprite::getDataId() { } void GameObjectSprite::setDataId( Uint32 Id ) { - Graphics::Sprite* tSprite = NULL; + Graphics::SpritePtr tSprite; if ( mFlags & GObjFlags::GAMEOBJECT_ANIMATED ) { std::vector tTextureRegionVec = @@ -145,7 +142,7 @@ void GameObjectSprite::setDataId( Uint32 Id ) { tSprite->createAnimation(); tSprite->addFrames( tTextureRegionVec ); - setSprite( tSprite ); + setSprite( std::move( tSprite ) ); } } else { Graphics::TextureRegion* tTextureRegion = diff --git a/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp b/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp index 77e7b1d23..23eeca3de 100644 --- a/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp +++ b/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp @@ -9,7 +9,8 @@ namespace EE { namespace Maps { GameObjectTextureRegion::GameObjectTextureRegion( const Uint32& Flags, MapLayer* Layer, Graphics::TextureRegion* TextureRegion, const Vector2f& Pos ) : - GameObject( Flags, Layer ), mTextureRegion( TextureRegion ), mPos( Pos ) { + GameObject( Flags, Layer ), mPos( Pos ) { + setTextureRegion( TextureRegion ); assignTilePos(); } @@ -44,7 +45,8 @@ void GameObjectTextureRegion::draw() { getRenderModeFromFlags() ); } else { mTextureRegion->draw( mPos.x, mPos.y, *LM->getTileColor( Tile ), getRotation(), - Vector2f::One, BlendMode::Alpha(), getRenderModeFromFlags() ); + Vector2f::One, BlendMode::Alpha(), + getRenderModeFromFlags() ); } } else { if ( LM->isByVertex() ) { @@ -99,11 +101,14 @@ Sizei GameObjectTextureRegion::getSize() { } Graphics::TextureRegion* GameObjectTextureRegion::getTextureRegion() const { - return mTextureRegion; + return mTextureRegion.get(); } void GameObjectTextureRegion::setTextureRegion( Graphics::TextureRegion* TextureRegion ) { - mTextureRegion = TextureRegion; + mTextureRegion = + TextureRegion + ? std::static_pointer_cast( TextureRegion->createInstance() ) + : TextureRegionPtr{}; } Uint32 GameObjectTextureRegion::getDataId() { diff --git a/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp b/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp index 45d5e592f..4d0a9b5aa 100644 --- a/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp +++ b/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp @@ -10,23 +10,16 @@ namespace EE { namespace Maps { GameObjectVirtual::GameObjectVirtual( Uint32 DataId, MapLayer* Layer, const Uint32& Flags, Uint32 Type, const Vector2f& Pos ) : - GameObject( Flags, Layer ), - mType( Type ), - mDataId( DataId ), - mPos( Pos ), - mLayer( NULL ), - mTextureRegion( NULL ) {} + GameObject( Flags, Layer ), mType( Type ), mDataId( DataId ), mPos( Pos ), mLayer( NULL ) {} GameObjectVirtual::GameObjectVirtual( TextureRegion* TextureRegion, MapLayer* Layer, const Uint32& Flags, Uint32 Type, const Vector2f& Pos ) : - GameObject( Flags, Layer ), - mType( Type ), - mDataId( 0 ), - mPos( Pos ), - mLayer( Layer ), - mTextureRegion( TextureRegion ) { - if ( NULL != TextureRegion ) + GameObject( Flags, Layer ), mType( Type ), mDataId( 0 ), mPos( Pos ), mLayer( Layer ) { + if ( NULL != TextureRegion ) { mDataId = TextureRegion->getId(); + mTextureRegion = std::static_pointer_cast( + TextureRegion->createInstance() ); + } } GameObjectVirtual::~GameObjectVirtual() {} @@ -86,9 +79,10 @@ void GameObjectVirtual::draw() { Vector2f( mPos.x + mTextureRegion->getDestSize().x, mPos.y ) ), getBlendModeFromFlags(), getRenderModeFromFlags() ); } else { - mTextureRegion->draw( - mPos.x, mPos.y, LM->getColorFromPos( Vector2f( mPos.x, mPos.y ) ), - getRotation(), Vector2f::One, BlendMode::Alpha(), getRenderModeFromFlags() ); + mTextureRegion->draw( mPos.x, mPos.y, + LM->getColorFromPos( Vector2f( mPos.x, mPos.y ) ), + getRotation(), Vector2f::One, BlendMode::Alpha(), + getRenderModeFromFlags() ); } } } else { diff --git a/src/modules/maps/src/eepp/maps/mapeditor/mapeditor.cpp b/src/modules/maps/src/eepp/maps/mapeditor/mapeditor.cpp index 09726d946..9dec79224 100644 --- a/src/modules/maps/src/eepp/maps/mapeditor/mapeditor.cpp +++ b/src/modules/maps/src/eepp/maps/mapeditor/mapeditor.cpp @@ -1440,15 +1440,17 @@ GameObject* MapEditor::createGameObject() { if ( mChkAnim->isChecked() ) { - Sprite* tAnimSprite = Sprite::New( + SpritePtr tAnimSprite = Sprite::New( String::removeNumbersAtEnd( mGfxPreview->getTextureRegion()->getName() ) ); - tObj = eeNew( GameObjectSprite, ( mCurGOFlags, mCurLayer, tAnimSprite ) ); tAnimSprite->setAutoAnimate( false ); + tObj = eeNew( GameObjectSprite, + ( mCurGOFlags, mCurLayer, std::move( tAnimSprite ) ) ); } else { - Sprite* tStaticSprite = Sprite::New( mGfxPreview->getTextureRegion() ); - tObj = eeNew( GameObjectSprite, ( mCurGOFlags, mCurLayer, tStaticSprite ) ); + SpritePtr tStaticSprite = Sprite::New( mGfxPreview->getTextureRegion() ); + tObj = eeNew( GameObjectSprite, + ( mCurGOFlags, mCurLayer, std::move( tStaticSprite ) ) ); } } else { //! Creates an empty game object. The client will interpret the GameObject Type, and diff --git a/src/modules/physics/include/eepp/physics/shapecirclesprite.hpp b/src/modules/physics/include/eepp/physics/shapecirclesprite.hpp index fd47accce..daeccdc2a 100644 --- a/src/modules/physics/include/eepp/physics/shapecirclesprite.hpp +++ b/src/modules/physics/include/eepp/physics/shapecirclesprite.hpp @@ -5,9 +5,7 @@ #ifdef PHYSICS_RENDERER_ENABLED -namespace EE { namespace Graphics { -class Sprite; -}} // namespace EE::Graphics +#include using namespace EE::Graphics; namespace EE { namespace Physics { @@ -15,10 +13,9 @@ namespace EE { namespace Physics { class EE_PHYSICS_API ShapeCircleSprite : public ShapeCircle { public: static ShapeCircleSprite* New( Physics::Body* body, cpFloat radius, cVect offset, - Sprite* Sprite, bool AutoDeleteSprite = false ); + SpritePtr sprite ); - ShapeCircleSprite( Physics::Body* body, cpFloat radius, cVect offset, Sprite* Sprite, - bool AutoDeleteSprite = false ); + ShapeCircleSprite( Physics::Body* body, cpFloat radius, cVect offset, SpritePtr sprite ); virtual ~ShapeCircleSprite(); @@ -28,11 +25,10 @@ class EE_PHYSICS_API ShapeCircleSprite : public ShapeCircle { virtual void setOffset( const cVect& offset ); - Sprite* getSprite() const; + const SpritePtr& getSprite() const; protected: - Sprite* mSprite; - bool mSpriteAutoDelete; + SpritePtr mSprite; void offsetSet(); }; diff --git a/src/modules/physics/include/eepp/physics/shapepolysprite.hpp b/src/modules/physics/include/eepp/physics/shapepolysprite.hpp index 193ecf3f4..a4d8a9440 100644 --- a/src/modules/physics/include/eepp/physics/shapepolysprite.hpp +++ b/src/modules/physics/include/eepp/physics/shapepolysprite.hpp @@ -5,9 +5,7 @@ #ifdef PHYSICS_RENDERER_ENABLED -namespace EE { namespace Graphics { -class Sprite; -}} // namespace EE::Graphics +#include using namespace EE::Graphics; namespace EE { namespace Physics { @@ -15,26 +13,24 @@ namespace EE { namespace Physics { class EE_PHYSICS_API ShapePolySprite : public ShapePoly { public: static ShapePolySprite* New( Physics::Body* body, int numVerts, cVect* verts, cVect offset, - Sprite* Sprite, bool AutoDeleteSprite = false ); + SpritePtr sprite ); - static ShapePolySprite* New( Physics::Body* body, cpFloat width, cpFloat height, Sprite* Sprite, - bool AutoDeleteSprite = false ); + static ShapePolySprite* New( Physics::Body* body, cpFloat width, cpFloat height, + SpritePtr sprite ); - ShapePolySprite( Physics::Body* body, int numVerts, cVect* verts, cVect offset, Sprite* Sprite, - bool AutoDeleteSprite = false ); + ShapePolySprite( Physics::Body* body, int numVerts, cVect* verts, cVect offset, + SpritePtr sprite ); - ShapePolySprite( Physics::Body* body, cpFloat width, cpFloat height, Sprite* Sprite, - bool AutoDeleteSprite = false ); + ShapePolySprite( Physics::Body* body, cpFloat width, cpFloat height, SpritePtr sprite ); virtual ~ShapePolySprite(); virtual void draw( Space* space ); - Sprite* getSprite() const; + const SpritePtr& getSprite() const; protected: - Sprite* mSprite; - bool mSpriteAutoDelete; + SpritePtr mSprite; Vector2i mOffset; void offsetSet( cVect center ); diff --git a/src/modules/physics/src/eepp/physics/shapecirclesprite.cpp b/src/modules/physics/src/eepp/physics/shapecirclesprite.cpp index f51331e3f..1f3f85e9d 100644 --- a/src/modules/physics/src/eepp/physics/shapecirclesprite.cpp +++ b/src/modules/physics/src/eepp/physics/shapecirclesprite.cpp @@ -7,20 +7,17 @@ namespace EE { namespace Physics { ShapeCircleSprite* ShapeCircleSprite::New( Physics::Body* body, cpFloat radius, cVect offset, - Sprite* Sprite, bool AutoDeleteSprite ) { - return eeNew( ShapeCircleSprite, ( body, radius, offset, Sprite, AutoDeleteSprite ) ); + SpritePtr sprite ) { + return eeNew( ShapeCircleSprite, ( body, radius, offset, std::move( sprite ) ) ); } ShapeCircleSprite::ShapeCircleSprite( Physics::Body* body, cpFloat radius, cVect offset, - Sprite* Sprite, bool AutoDeleteSprite ) : - ShapeCircle( body, radius, offset ), mSprite( Sprite ), mSpriteAutoDelete( AutoDeleteSprite ) { + SpritePtr sprite ) : + ShapeCircle( body, radius, offset ), mSprite( std::move( sprite ) ) { offsetSet(); } -ShapeCircleSprite::~ShapeCircleSprite() { - if ( mSpriteAutoDelete ) - eeSAFE_DELETE( mSprite ); -} +ShapeCircleSprite::~ShapeCircleSprite() {} void ShapeCircleSprite::draw( Space* space ) { cVect Pos = getBody()->getPos(); @@ -36,7 +33,7 @@ void ShapeCircleSprite::offsetSet() { -ShapeCircle::getRadius() + ShapeCircle::getOffset().y ) ); } -Sprite* ShapeCircleSprite::getSprite() const { +const SpritePtr& ShapeCircleSprite::getSprite() const { return mSprite; } diff --git a/src/modules/physics/src/eepp/physics/shapepolysprite.cpp b/src/modules/physics/src/eepp/physics/shapepolysprite.cpp index 1696964e3..6fdfa39dd 100644 --- a/src/modules/physics/src/eepp/physics/shapepolysprite.cpp +++ b/src/modules/physics/src/eepp/physics/shapepolysprite.cpp @@ -7,34 +7,31 @@ namespace EE { namespace Physics { ShapePolySprite* ShapePolySprite::New( Physics::Body* body, int numVerts, cVect* verts, - cVect offset, Sprite* Sprite, bool AutoDeleteSprite ) { - return eeNew( ShapePolySprite, ( body, numVerts, verts, offset, Sprite, AutoDeleteSprite ) ); + cVect offset, SpritePtr sprite ) { + return eeNew( ShapePolySprite, + ( body, numVerts, verts, offset, std::move( sprite ) ) ); } ShapePolySprite* ShapePolySprite::New( Physics::Body* body, cpFloat width, cpFloat height, - Sprite* Sprite, bool AutoDeleteSprite ) { - return eeNew( ShapePolySprite, ( body, width, height, Sprite, AutoDeleteSprite ) ); + SpritePtr sprite ) { + return eeNew( ShapePolySprite, ( body, width, height, std::move( sprite ) ) ); } ShapePolySprite::ShapePolySprite( Physics::Body* body, int numVerts, cVect* verts, cVect offset, - Sprite* Sprite, bool AutoDeleteSprite ) : + SpritePtr sprite ) : ShapePoly( body, numVerts, verts, offset ), - mSprite( Sprite ), - mSpriteAutoDelete( AutoDeleteSprite ) { + mSprite( std::move( sprite ) ) { offsetSet( centroid( numVerts, verts ) ); } ShapePolySprite::ShapePolySprite( Physics::Body* body, cpFloat width, cpFloat height, - Sprite* Sprite, bool AutoDeleteSprite ) : - ShapePoly( body, width, height ), mSprite( Sprite ), mSpriteAutoDelete( AutoDeleteSprite ) { + SpritePtr sprite ) : + ShapePoly( body, width, height ), mSprite( std::move( sprite ) ) { mSprite->setSize( Sizef( width, height ) ); offsetSet( cVectNew( width / 2, height / 2 ) ); } -ShapePolySprite::~ShapePolySprite() { - if ( mSpriteAutoDelete ) - eeSAFE_DELETE( mSprite ); -} +ShapePolySprite::~ShapePolySprite() {} void ShapePolySprite::draw( Space* space ) { cVect Pos = getBody()->getPos(); @@ -52,7 +49,7 @@ void ShapePolySprite::offsetSet( cVect center ) { ( Int32 )( -myCenter.y + ( center.y - myCenter.y ) ) ); } -Sprite* ShapePolySprite::getSprite() const { +const SpritePtr& ShapePolySprite::getSprite() const { return mSprite; } diff --git a/src/tests/test_all/test.cpp b/src/tests/test_all/test.cpp index 1ac2f1bbf..2e33477b7 100644 --- a/src/tests/test_all/test.cpp +++ b/src/tests/test_all/test.cpp @@ -379,7 +379,6 @@ void EETest::createBaseUI() { sprite->setSprite( Sprite::New( "gn" ) ); sprite->setParent( C ); sprite->setPosition( 160, 100 ); - sprite->setIsSpriteOwner( true ); UITextView* Text = UITextView::New(); Text->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ) @@ -742,7 +741,7 @@ void EETest::createNewUI() { UISprite* sprite = UISprite::New(); sprite->setFlags( UI_AUTO_SIZE ); sprite->setPosition( 50, 600 )->setParent( container ); - sprite->setSprite( &SP ); + sprite->setSprite( SP.clone() ); UIScrollBar* scrollBar = UIScrollBar::New(); scrollBar->setOrientation( UIOrientation::Horizontal ) @@ -1802,26 +1801,26 @@ void EETest::screen5() { Color col( 0x000000CC ); if ( drawableGroup.getDrawableCount() == 0 ) { - ArcDrawable* arc = ArcDrawable::New(); + auto arc = makeResource(); arc->setPosition( Vector2f( 60, 60 ) ); arc->setArcStartAngle( 90 ); arc->setArcAngle( 180 ); arc->setRadius( 60 ); arc->setColor( col ); - RectangleDrawable* rect = RectangleDrawable::New(); + auto rect = makeResource(); rect->setPosition( Vector2f( 0, 60 ) ); rect->setSize( Sizef( 120, 60 ) ); rect->setColor( col ); - ArcDrawable* arc2 = ArcDrawable::New(); + auto arc2 = makeResource(); arc2->setPosition( Vector2f( 60, 120 ) ); arc2->setArcStartAngle( -90 ); arc2->setArcAngle( 180 ); arc2->setRadius( 60 ); arc2->setColor( col ); - ConvexShapeDrawable* poly = ConvexShapeDrawable::New(); + auto poly = makeResource(); poly->setPosition( Vector2f( 60, 90 ) ); poly->addPoint( Vector2f( -10, -10 ) ); poly->addPoint( Vector2f( -10, 10 ) ); @@ -2551,8 +2550,8 @@ void EETest::end() { eeSAFE_DELETE( mTGL ); eeSAFE_DELETE( mFBO ); eeSAFE_DELETE( mVBO ); - eeSAFE_DELETE( mBoxSprite ); - eeSAFE_DELETE( mCircleSprite ); + mBoxSprite.reset(); + mCircleSprite.reset(); eeSAFE_DELETE( PakTest ); Log::instance()->save(); diff --git a/src/tests/test_all/test.hpp b/src/tests/test_all/test.hpp index 86f159690..5e992ec20 100644 --- a/src/tests/test_all/test.hpp +++ b/src/tests/test_all/test.hpp @@ -225,8 +225,8 @@ class EETest : private Thread { std::vector mDemo; Uint32 mCurDemo; - Sprite* mBoxSprite; - Sprite* mCircleSprite; + SpritePtr mBoxSprite; + SpritePtr mCircleSprite; UITheme* mTheme; UISceneNode* mSceneNode; diff --git a/src/tests/unit_tests/drawableimageparser_tests.cpp b/src/tests/unit_tests/drawableimageparser_tests.cpp index ebc0a69d6..7416bbf5b 100644 --- a/src/tests/unit_tests/drawableimageparser_tests.cpp +++ b/src/tests/unit_tests/drawableimageparser_tests.cpp @@ -53,15 +53,13 @@ UTEST( DrawableImageParser, TwoStopsWithPosition ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "linear-gradient(#f5eedd 0%, #ebe0c2 100%)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "linear-gradient(#f5eedd 0%, #ebe0c2 100%)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - ASSERT_TRUE( ownIt ); ASSERT_EQ( drawable->getDrawableType(), Drawable::LINEARGRADIENT ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); EXPECT_EQ( stops[0].value, 0.f ); @@ -72,7 +70,6 @@ UTEST( DrawableImageParser, TwoStopsWithPosition ) { EXPECT_TRUE( stops[1].color == expectEbe ); EXPECT_EQ( grad->getAngle(), 180.f ); /* default: to bottom */ - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -85,18 +82,16 @@ UTEST( DrawableImageParser, DirectionToRight ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "linear-gradient(to right, red, blue)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "linear-gradient(to right, red, blue)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_EQ( grad->getAngle(), 90.f ); EXPECT_EQ( grad->getColorStops().size(), (size_t)2 ); EXPECT_TRUE( grad->getColorStops()[0].color == Color::fromString( "red" ) ); EXPECT_TRUE( grad->getColorStops()[1].color == Color::fromString( "blue" ) ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -109,16 +104,14 @@ UTEST( DrawableImageParser, AngleDegrees ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "linear-gradient(45deg, red 0%, blue 100%)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "linear-gradient(45deg, red 0%, blue 100%)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_EQ( grad->getAngle(), 45.f ); EXPECT_EQ( grad->getColorStops().size(), (size_t)2 ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -131,16 +124,14 @@ UTEST( DrawableImageParser, AngleTurn ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "linear-gradient(0.25turn, red, blue)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "linear-gradient(0.25turn, red, blue)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_EQ( grad->getAngle(), 90.f ); /* 0.25 * 360 = 90 */ EXPECT_EQ( grad->getColorStops().size(), (size_t)2 ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -153,12 +144,11 @@ UTEST( DrawableImageParser, ThreeStops ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "linear-gradient(red 0%, green 50%, blue 100%)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "linear-gradient(red 0%, green 50%, blue 100%)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)3 ); EXPECT_EQ( stops[0].value, 0.f ); @@ -166,7 +156,6 @@ UTEST( DrawableImageParser, ThreeStops ) { EXPECT_TRUE( stops[1].color == Color::fromString( "green" ) ); EXPECT_TRUE( stops[2].color == Color::fromString( "blue" ) ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -179,12 +168,11 @@ UTEST( DrawableImageParser, ColorHint ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "linear-gradient(red 0%, 25%, blue 100%)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "linear-gradient(red 0%, 25%, blue 100%)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); const auto& stops = grad->getColorStops(); /* Hint expands to 16 sampled sub-stops + 2 color stops = 18 total @@ -209,7 +197,6 @@ UTEST( DrawableImageParser, ColorHint ) { } EXPECT_TRUE( foundMidpoint ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -222,12 +209,11 @@ UTEST( DrawableImageParser, StopsWithoutPositions ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "linear-gradient(red, green, blue)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "linear-gradient(red, green, blue)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)3 ); /* Should be evenly distributed: 0%, 50%, 100% */ @@ -235,7 +221,6 @@ UTEST( DrawableImageParser, StopsWithoutPositions ) { EXPECT_NEAR( stops[1].value, 50.f, 0.01f ); EXPECT_EQ( stops[2].value, 100.f ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -248,17 +233,15 @@ UTEST( DrawableImageParser, EmptyAngleFallsThrough ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; /* No direction specified, should default to 180 (to bottom) */ - Drawable* drawable = - parser.createDrawable( "linear-gradient(red, blue)", Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = + parser.createDrawable( "linear-gradient(red, blue)", Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_EQ( grad->getAngle(), 180.f ); EXPECT_EQ( grad->getColorStops().size(), (size_t)2 ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -271,20 +254,18 @@ UTEST( DrawableImageParser, RepeatingTwoStops ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "repeating-linear-gradient(red, blue)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "repeating-linear-gradient(red, blue)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); ASSERT_TRUE( drawable->getDrawableType() == Drawable::REPEATINGLINEARGRADIENT ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); EXPECT_EQ( stops[0].value, 0.f ); EXPECT_EQ( stops[1].value, 100.f ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -297,18 +278,16 @@ UTEST( DrawableImageParser, RepeatingWithAngle ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( - "repeating-linear-gradient(45deg, #f00, #0f0, #00f)", Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( + "repeating-linear-gradient(45deg, #f00, #0f0, #00f)", Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_EQ( grad->getAngle(), 45.f ); EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)3 ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -321,19 +300,17 @@ UTEST( DrawableImageParser, RepeatingWithPositions ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "repeating-linear-gradient(red 10%, blue 40%)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "repeating-linear-gradient(red 10%, blue 40%)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); EXPECT_EQ( stops[0].value, 10.f ); EXPECT_EQ( stops[1].value, 40.f ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -346,20 +323,18 @@ UTEST( DrawableImageParser, RadialTwoStops ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = - parser.createDrawable( "radial-gradient(red, blue)", Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = + parser.createDrawable( "radial-gradient(red, blue)", Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); ASSERT_TRUE( drawable->getDrawableType() == Drawable::RADIALGRADIENT ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_FALSE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); EXPECT_EQ( stops[0].value, 0.f ); EXPECT_EQ( stops[1].value, 1.f ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -372,16 +347,14 @@ UTEST( DrawableImageParser, RadialCircleKeyword ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "radial-gradient(circle, #f00, #00f)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "radial-gradient(circle, #f00, #00f)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_EQ( grad->getShape(), RadialGradientDrawable::CIRCLE ); EXPECT_EQ( grad->getColorStops().size(), (size_t)2 ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -394,18 +367,16 @@ UTEST( DrawableImageParser, RadialWithPositions ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "radial-gradient(red 10%, blue 80%)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "radial-gradient(red 10%, blue 80%)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); EXPECT_EQ( stops[0].value, 10.f ); EXPECT_EQ( stops[1].value, 80.f ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -418,20 +389,18 @@ UTEST( DrawableImageParser, RepeatingRadial ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( "repeating-radial-gradient(red 10%, blue 40%)", - Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( "repeating-radial-gradient(red 10%, blue 40%)", + Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); ASSERT_TRUE( drawable->getDrawableType() == Drawable::REPEATINGRADIALGRADIENT ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); ASSERT_EQ( stops.size(), (size_t)2 ); EXPECT_EQ( stops[0].value, 10.f ); EXPECT_EQ( stops[1].value, 40.f ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } @@ -444,12 +413,11 @@ UTEST( DrawableImageParser, TwoLengthStopSyntax ) { UINode* node = createTestNode( sceneNode ); auto& parser = StyleSheetSpecification::instance()->getDrawableImageParser(); - bool ownIt = false; - Drawable* drawable = parser.createDrawable( - "repeating-linear-gradient(red 0 10px, blue 10px 20px)", Sizef( 100, 100 ), ownIt, node ); + DrawablePtr drawable = parser.createDrawable( + "repeating-linear-gradient(red 0 10px, blue 10px 20px)", Sizef( 100, 100 ), node ); ASSERT_TRUE( drawable != NULL ); - auto* grad = static_cast( drawable ); + auto* grad = static_cast( drawable.get() ); EXPECT_TRUE( grad->isRepeating() ); const auto& stops = grad->getColorStops(); /* red 0 10px → two stops: raw pos 0 and raw pos 10. blue 10px 20px → @@ -465,6 +433,5 @@ UTEST( DrawableImageParser, TwoLengthStopSyntax ) { EXPECT_EQ( stops[3].value, 20.f ); EXPECT_EQ( stops[3].unit, EE::UI::CSS::StyleSheetLength::Px ); - eeSAFE_DELETE( drawable ); Engine::destroySingleton(); } diff --git a/src/tests/unit_tests/resource_prerequisite_tests.cpp b/src/tests/unit_tests/resource_prerequisite_tests.cpp index ba8e2394f..63c2e04e8 100644 --- a/src/tests/unit_tests/resource_prerequisite_tests.cpp +++ b/src/tests/unit_tests/resource_prerequisite_tests.cpp @@ -4,27 +4,35 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include +#include #include +#include +#include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -88,6 +96,44 @@ class TestTextureAtlasLoader : public TextureAtlasLoader { } }; +class TestDrawableResource : public DrawableResource { + public: + TestDrawableResource() : DrawableResource( Drawable::CUSTOM ) {} + + Sizef getSize() { return {}; } + Sizef getPixelsSize() { return {}; } + void draw() {} + void draw( const Vector2f& ) {} + void draw( const Vector2f&, const Sizef& ) {} + bool isStateful() { return false; } + + void notifyChange() { onResourceChange(); } +}; + +class CountingDrawable : public Drawable { + public: + CountingDrawable( std::shared_ptr instanceCount ) : + Drawable( Drawable::CUSTOM ), mInstanceCount( std::move( instanceCount ) ) {} + + Sizef getSize() { return { 16.f, 16.f }; } + Sizef getPixelsSize() { return getSize(); } + void draw() {} + void draw( const Vector2f& ) {} + void draw( const Vector2f&, const Sizef& ) {} + bool isStateful() { return false; } + + DrawablePtr createInstance() const { + ++*mInstanceCount; + auto instance = makeResource( mInstanceCount ); + instance->setColor( mColor ); + instance->setPosition( mPosition ); + return instance; + } + + private: + std::shared_ptr mInstanceCount; +}; + class AsyncDeliveryProducerScene : public UISceneNode { public: static AsyncDeliveryProducerScene* New( EE::Window::Window* window ) { @@ -396,13 +442,6 @@ UTEST( ResourcePrerequisites, pendingBatchRetainsTextureUntilDisplayCollection ) TexturePtr texture = loader.getTexture(); ASSERT_TRUE( texture != nullptr ); - bool unloaded = false; - texture->pushResourceChangeCallback( - [&unloaded]( Uint32, DrawableResource::Event event, DrawableResource* ) { - if ( event == DrawableResource::Event::Unload ) - unloaded = true; - } ); - TextureRegistrySnapshot snapshot = factory->snapshotTextures(); auto record = std::find_if( snapshot.begin(), snapshot.end(), [&texture]( const auto& entry ) { return entry.id == texture->getTextureId(); @@ -419,13 +458,11 @@ UTEST( ResourcePrerequisites, pendingBatchRetainsTextureUntilDisplayCollection ) retainedTexture.reset(); EXPECT_FALSE( weakTexture.expired() ); - EXPECT_FALSE( unloaded ); EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); window->display( false ); EXPECT_TRUE( weakTexture.expired() ); - EXPECT_TRUE( unloaded ); EXPECT_EQ( factory->getPendingReleaseCount(), static_cast( 0 ) ); Engine::destroySingleton(); } @@ -488,6 +525,223 @@ UTEST( ResourcePrerequisites, textureRegionRetainsItsTexture ) { Engine::destroySingleton(); } +UTEST( ResourcePrerequisites, drawableResourceConnectionsDisconnectWithTheirLifetime ) { + auto resource = makeResource(); + int notifications = 0; + { + DrawableResourceConnection connection = resource->connectResourceChange( + [¬ifications]( DrawableResource& ) { ++notifications; } ); + EXPECT_TRUE( static_cast( connection ) ); + resource->notifyChange(); + EXPECT_EQ( notifications, 1 ); + } + + resource->notifyChange(); + EXPECT_EQ( notifications, 1 ); + + DrawableResourceConnection expiredConnection = + resource->connectResourceChange( []( DrawableResource& ) {} ); + resource.reset(); + EXPECT_FALSE( static_cast( expiredConnection ) ); + expiredConnection.disconnect(); +} + +UTEST( ResourcePrerequisites, textureCreatesIndependentDrawableInstances ) { + EE::Window::Window* window = createLifecycleTestWindow( "Texture drawable instance test" ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 2, 2 ); + ASSERT_TRUE( texture != nullptr ); + + DrawablePtr firstDrawable = texture->createInstance(); + DrawablePtr secondDrawable = texture->createInstance(); + ASSERT_TRUE( firstDrawable != nullptr ); + ASSERT_TRUE( secondDrawable != nullptr ); + ASSERT_EQ( firstDrawable->getDrawableType(), Drawable::TEXTUREDRAWABLE ); + ASSERT_EQ( secondDrawable->getDrawableType(), Drawable::TEXTUREDRAWABLE ); + auto first = std::static_pointer_cast( firstDrawable ); + auto second = std::static_pointer_cast( secondDrawable ); + ASSERT_TRUE( first != nullptr ); + ASSERT_TRUE( second != nullptr ); + EXPECT_NE( first.get(), second.get() ); + EXPECT_EQ( first->getTexture().get(), texture.get() ); + EXPECT_EQ( second->getTexture().get(), texture.get() ); + + first->setColor( Color::Red ); + second->setColor( Color::Blue ); + first->setPosition( { 3.f, 4.f } ); + EXPECT_TRUE( first->getColor() == Color::Red ); + EXPECT_TRUE( second->getColor() == Color::Blue ); + EXPECT_TRUE( second->getPosition() == Vector2f::Zero ); + + firstDrawable.reset(); + secondDrawable.reset(); + first.reset(); + second.reset(); + texture.reset(); + window->display( false ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, uiIconSeparatesSourceLookupFromInstanceCreation ) { + auto instanceCount = std::make_shared( 0 ); + DrawablePtr source = makeResource( instanceCount ); + UIIcon* icon = UIIcon::New( "counting-icon" ); + icon->setSource( 16, source ); + + const DrawablePtr& exactSource = icon->getSource( 16 ); + const DrawablePtr& closestSource = icon->getSource( 14 ); + EXPECT_EQ( exactSource.get(), source.get() ); + EXPECT_EQ( closestSource.get(), source.get() ); + EXPECT_EQ( *instanceCount, 0 ); + + DrawablePtr first = icon->createDrawable( 16 ); + DrawablePtr second = icon->createDrawable( 14 ); + ASSERT_TRUE( first != nullptr ); + ASSERT_TRUE( second != nullptr ); + EXPECT_EQ( *instanceCount, 2 ); + EXPECT_NE( first.get(), second.get() ); + EXPECT_NE( first.get(), source.get() ); + + first->setColor( Color::Red ); + EXPECT_TRUE( source->getColor() == Color::White ); + EXPECT_TRUE( second->getColor() == Color::White ); + + eeDelete( icon ); +} + +UTEST( ResourcePrerequisites, stateListsCloneStateAndChildrenIndependently ) { + EE::Window::Window* window = createLifecycleTestWindow( "State list instance test" ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 2, 2 ); + ASSERT_TRUE( texture != nullptr ); + auto firstRegion = makeResource( texture ); + auto secondRegion = makeResource( texture ); + auto source = ResourcePtr( StateListDrawable::New(), + ResourceDeleter() ); + source->setStateDrawable( 1, firstRegion ); + source->setStateDrawable( 2, secondRegion ); + source->setState( 1 ); + + DrawablePtr drawableInstance = source->createInstance(); + ASSERT_TRUE( drawableInstance != nullptr ); + ASSERT_EQ( drawableInstance->getDrawableType(), Drawable::STATELIST ); + auto instance = std::static_pointer_cast( drawableInstance ); + ASSERT_TRUE( instance != nullptr ); + EXPECT_EQ( source->getState(), 1u ); + EXPECT_EQ( instance->getState(), 1u ); + EXPECT_NE( source->getStateDrawable( 1 ), instance->getStateDrawable( 1 ) ); + + instance->setState( 2 ); + instance->setStateColor( 1, Color::Red ); + EXPECT_EQ( source->getState(), 1u ); + EXPECT_EQ( instance->getState(), 2u ); + EXPECT_TRUE( source->getStateDrawable( 1 )->getColor() == Color::White ); + EXPECT_TRUE( instance->getStateDrawable( 1 )->getColor() == Color::Red ); + EXPECT_FALSE( source->hasDrawableState( 99 ) ); + source->setState( 99 ); + EXPECT_FALSE( source->hasDrawableState( 99 ) ); + + instance.reset(); + drawableInstance.reset(); + source.reset(); + firstRegion.reset(); + secondRegion.reset(); + texture.reset(); + window->display( false ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, spritesCloneFramesAndAnimationStateIndependently ) { + EE::Window::Window* window = createLifecycleTestWindow( "Sprite instance test" ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 2, 2 ); + ASSERT_TRUE( texture != nullptr ); + SpritePtr source = Sprite::New(); + source->createAnimation(); + source->addFrame( texture, Sizef( 2.f, 2.f ) ); + source->addFrame( texture, Sizef( 2.f, 2.f ) ); + source->setCurrentFrame( 0 ); + + SpritePtr instance = source->clone(); + ASSERT_TRUE( instance != nullptr ); + TextureRegion* sourceFrame = source->getTextureRegion( 0 ); + TextureRegion* instanceFrame = instance->getTextureRegion( 0 ); + ASSERT_TRUE( sourceFrame != nullptr ); + ASSERT_TRUE( instanceFrame != nullptr ); + EXPECT_NE( sourceFrame, instanceFrame ); + EXPECT_EQ( sourceFrame->getTexture().get(), instanceFrame->getTexture().get() ); + + instance->setCurrentFrame( 2 ); + instanceFrame->setDestSize( Sizef( 8.f, 9.f ) ); + EXPECT_EQ( source->getCurrentFrame(), 0u ); + EXPECT_EQ( instance->getCurrentFrame(), 1u ); + EXPECT_TRUE( sourceFrame->getDestSize() == Sizef( 2.f, 2.f ) ); + EXPECT_TRUE( instanceFrame->getDestSize() == Sizef( 8.f, 9.f ) ); + + instance.reset(); + source.reset(); + texture.reset(); + window->display( false ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, regionConsumersDoNotMutateSharedSourceGeometryWhileDrawing ) { + EE::Window::Window* window = createLifecycleTestWindow( "Texture region draw isolation test" ); + TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 8, 8 ); + ASSERT_TRUE( texture != nullptr ); + auto source = makeResource( texture, Rect( 1, 2, 7, 8 ), Sizef( 6.f, 6.f ), + Vector2i( 2, 3 ) ); + const Rect originalRect = source->getSrcRect(); + const Sizef originalSize = source->getDestSize(); + const Vector2i originalOffset = source->getOffset(); + + UITextureRegion* widget = UITextureRegion::New(); + widget->setTextureRegion( source.get() ); + widget->setSize( Sizef( 20.f, 20.f ) ); + widget->setScaleType( UIScaleType::Expand ); + widget->draw(); + EXPECT_TRUE( source->getSrcRect() == originalRect ); + EXPECT_TRUE( source->getDestSize() == originalSize ); + EXPECT_TRUE( source->getOffset() == originalOffset ); + eeDelete( widget ); + + { + ScrollParallax parallax( source.get(), Vector2f::Zero, Sizef( 20.f, 20.f ) ); + ASSERT_TRUE( parallax.getTextureRegion() != nullptr ); + EXPECT_NE( parallax.getTextureRegion(), source.get() ); + EXPECT_EQ( parallax.getTextureRegion()->getTexture().get(), texture.get() ); + parallax.draw(); + EXPECT_TRUE( source->getSrcRect() == originalRect ); + EXPECT_TRUE( source->getDestSize() == originalSize ); + EXPECT_TRUE( source->getOffset() == originalOffset ); + } + + source.reset(); + texture.reset(); + window->display( false ); + Engine::destroySingleton(); +} + +UTEST( ResourcePrerequisites, drawableGroupsAndVariantsHoldSafeIndependentHandles ) { + auto source = DrawableGroup::New(); + auto rectangle = makeResource( Vector2f( 1.f, 2.f ), Sizef( 3.f, 4.f ) ); + source->addDrawable( rectangle ); + + DrawablePtr drawableInstance = source->createInstance(); + ASSERT_TRUE( drawableInstance != nullptr ); + ASSERT_EQ( drawableInstance->getDrawableType(), Drawable::GROUP ); + auto instance = std::static_pointer_cast( drawableInstance ); + ASSERT_TRUE( instance != nullptr ); + ASSERT_EQ( instance->getDrawableCount(), 1u ); + EXPECT_NE( source->getGroup()[0].get(), instance->getGroup()[0].get() ); + instance->getGroup()[0]->setColor( Color::Blue ); + EXPECT_TRUE( source->getGroup()[0]->getColor() == Color::White ); + EXPECT_TRUE( instance->getGroup()[0]->getColor() == Color::Blue ); + + Variant original( instance ); + Variant copied( original ); + instance.reset(); + EXPECT_TRUE( original.asDrawable() != nullptr ); + EXPECT_EQ( original.asDrawable().get(), copied.asDrawable().get() ); +} + UTEST( ResourcePrerequisites, unsignedVariantPreservesTypeAndValue ) { const unsigned int value = std::numeric_limits::max(); Variant original( value ); @@ -644,7 +898,7 @@ UTEST( ResourcePrerequisites, engineTeardownReleasesGraphicsBeforeContextsAcross auto* scene = UISceneNode::New(); SceneManager::instance()->add( scene ); scene->enableFrameBuffer(); - UIImage::New()->setDrawable( ninePatch )->setParent( scene->getRoot() ); + UIImage::New()->setDrawable( ninePatch->createInstance() )->setParent( scene->getRoot() ); auto* font = FontTrueType::New( "engine-teardown-font" ); ASSERT_TRUE( diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index e4cfef37f..581587497 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -54,6 +56,25 @@ using namespace EE::Scene; using namespace EE::UI; using namespace EE::UI::Tools; +static Texture* getDrawableTexture( Drawable* drawable ) { + if ( !drawable ) + return nullptr; + switch ( drawable->getDrawableType() ) { + case Drawable::TEXTURE: + return static_cast( drawable ); + case Drawable::TEXTUREDRAWABLE: + return static_cast( drawable )->getTexture().get(); + case Drawable::TEXTUREREGION: + return static_cast( drawable )->getTexture().get(); + default: + return nullptr; + } +} + +static Texture* getDrawableTexture( const DrawablePtr& drawable ) { + return getDrawableTexture( drawable.get() ); +} + static void init_ui_test() { Engine::instance()->createWindow( WindowSettings( 1024, 650, "HTML Tables Test", WindowStyle::Default, WindowBackend::Default, @@ -3843,8 +3864,11 @@ UTEST( UIBackground, RemoteImageReusesCachedTexture ) { ASSERT_TRUE( second->getBackground() != nullptr ); ASSERT_TRUE( first->getBackground()->getLayer( 0 ) != nullptr ); ASSERT_TRUE( second->getBackground()->getLayer( 0 ) != nullptr ); - EXPECT_EQ( cached.get(), first->getBackground()->getLayer( 0 )->getDrawable() ); - EXPECT_EQ( cached.get(), second->getBackground()->getLayer( 0 )->getDrawable() ); + const DrawablePtr& firstDrawable = first->getBackground()->getLayer( 0 )->getDrawable(); + const DrawablePtr& secondDrawable = second->getBackground()->getLayer( 0 )->getDrawable(); + EXPECT_EQ( cached.get(), getDrawableTexture( firstDrawable ) ); + EXPECT_EQ( cached.get(), getDrawableTexture( secondDrawable ) ); + EXPECT_NE( firstDrawable.get(), secondDrawable.get() ); cached.reset(); Engine::destroySingleton(); @@ -5118,7 +5142,7 @@ UTEST( UIHTML, DeferredFileImageReusesCachedTexture ) { sceneNode->setURI( URI( "file://" + processPath ) ); URI imageURI = sceneNode->solveRelativePath( URI( "../assets/icon/ee.png" ) ); ASSERT_TRUE( FileSystem::fileExists( imageURI.getFSPath() ) ); - Drawable* cached = DrawableSearcher::searchByName( + DrawablePtr cached = DrawableSearcher::searchByName( imageURI.toString(), false, sceneNode->getReferer(), sceneNode->getResourceScope().get() ); ASSERT_TRUE( cached != nullptr ); @@ -5145,9 +5169,11 @@ UTEST( UIHTML, DeferredFileImageReusesCachedTexture ) { auto* second = secondNode->asType(); ASSERT_TRUE( first != nullptr ); ASSERT_TRUE( second != nullptr ); - EXPECT_EQ( cached, first->getDrawable() ); - EXPECT_EQ( cached, second->getDrawable() ); + EXPECT_EQ( getDrawableTexture( cached ), getDrawableTexture( first->getDrawable() ) ); + EXPECT_EQ( getDrawableTexture( cached ), getDrawableTexture( second->getDrawable() ) ); + EXPECT_NE( first->getDrawable().get(), second->getDrawable().get() ); + cached.reset(); Engine::destroySingleton(); } @@ -5187,8 +5213,9 @@ UTEST( UIHTML, RemoteImageReusesCachedTexture ) { auto* second = secondNode->asType(); ASSERT_TRUE( first != nullptr ); ASSERT_TRUE( second != nullptr ); - EXPECT_EQ( cached.get(), first->getDrawable() ); - EXPECT_EQ( cached.get(), second->getDrawable() ); + EXPECT_EQ( cached.get(), getDrawableTexture( first->getDrawable() ) ); + EXPECT_EQ( cached.get(), getDrawableTexture( second->getDrawable() ) ); + EXPECT_NE( first->getDrawable().get(), second->getDrawable().get() ); cached.reset(); Engine::destroySingleton(); diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index a0fb7d418..0cffcc316 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -838,7 +838,7 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j, editorSplitter->removeUnusedTab( curTabWidget, true, false ); if ( icon ) - tab->setIcon( icon ); + tab->setIcon( std::move( icon ) ); if ( curTabWidget->getTabCount() == totalToLoad ) curTabWidget->setTabSelected( diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index ef2786ace..09b92cb73 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -284,7 +284,7 @@ struct SessionSnapshotFile { struct TabWidgetData { UIWidget* widget{ nullptr }; - Drawable* icon{ nullptr }; + DrawablePtr icon; std::string title; }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index dee51bd74..e96c71cbb 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1316,17 +1316,17 @@ void App::setFocusEditorOnClose( UIMessageBox* msgBox ) { } ); } -Drawable* App::findIcon( const std::string& name ) { +DrawablePtr App::findIcon( const std::string& name ) { return findIcon( name, mMenuIconSize ); } -Drawable* App::findIcon( const std::string& name, const size_t iconSize ) { +DrawablePtr App::findIcon( const std::string& name, const size_t iconSize ) { if ( name.empty() ) - return nullptr; + return {}; UIIcon* icon = mUISceneNode->findIcon( name ); if ( icon ) - return icon->getSize( iconSize ); - return nullptr; + return icon->createDrawable( iconSize ); + return {}; } String App::i18n( const std::string& key, const String& def ) { @@ -2599,7 +2599,7 @@ void App::loadDiffFromMemory( const std::string& content, const std::string& ori auto scrollView = UIDiffView::NewMultiFileDiffViewer( content, repoPath ); auto [tab, iv] = getSplitter()->createWidget( scrollView, diffViewTitle ); if ( icon ) - tab->setIcon( icon->getSize( getMenuIconSize() ) ); + tab->setIcon( icon->createDrawable( getMenuIconSize() ) ); tab->setText( diffViewTitle ); auto diffView = scrollView->getFirstChild()->asType()->getFirstChild(); @@ -2629,7 +2629,7 @@ void App::loadDiffFromMemory( const std::string& content, const std::string& ori if ( !icon ) icon = getUISceneNode()->findIcon( "file" ); if ( icon ) - tab->setIcon( icon->getSize( getMenuIconSize() ) ); + tab->setIcon( icon->createDrawable( getMenuIconSize() ) ); diffView->setHeadersVisible( true ); diffView->loadFromPatch( content, originalFilePath, oldFilePath ); diffView->setSyntaxColorScheme( *getCurrentColorScheme() ); @@ -2651,7 +2651,7 @@ void App::loadDiffFromPath( const std::string& path ) { auto scrollView = UIDiffView::NewMultiFileDiffViewer( content ); auto [tab, iv] = getSplitter()->createWidget( scrollView, diffViewTitle ); if ( icon ) - tab->setIcon( icon->getSize( getMenuIconSize() ) ); + tab->setIcon( icon->createDrawable( getMenuIconSize() ) ); tab->setText( diffViewTitle ); auto diffView = scrollView->getFirstChild()->asType()->getFirstChild(); @@ -3158,7 +3158,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { return; if ( editor->getData() ) { UITab* tab = (UITab*)editor->getData(); - tab->setIcon( icon->getSize( mMenuIconSize ) ); + tab->setIcon( icon->createDrawable( mMenuIconSize ) ); } editor->getDocument().setHExtLanguageType( mProjectDocConfig.hExtLanguageType ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index a6f20ec70..e5b3927da 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -167,9 +167,9 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::string getCurrentFileDir() const; - Drawable* findIcon( const std::string& name ); + DrawablePtr findIcon( const std::string& name ); - Drawable* findIcon( const std::string& name, const size_t iconSize ); + DrawablePtr findIcon( const std::string& name, const size_t iconSize ); const std::map& getRealDefaultKeybindings(); diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index 69a308b8d..87aa68c15 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -2695,13 +2695,13 @@ void LLMChatUI::toggleEnableChats( bool enabled ) { toggleEnableChat( chat, enabled ); } -Drawable* LLMChatUI::findIcon( const std::string& name, const size_t iconSize ) { +DrawablePtr LLMChatUI::findIcon( const std::string& name, const size_t iconSize ) { if ( name.empty() ) - return nullptr; + return {}; UIIcon* icon = getUISceneNode()->findIcon( name ); if ( icon ) - return icon->getSize( iconSize ); - return nullptr; + return icon->createDrawable( iconSize ); + return {}; } void LLMChatUI::addPermissionUI( const acp::RequestPermissionRequest& req, diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp index e82a971d2..144553626 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.hpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp @@ -190,7 +190,7 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter { void toggleEnableChats( bool enabled ); - Drawable* findIcon( const std::string& name, const size_t iconSize ); + DrawablePtr findIcon( const std::string& name, const size_t iconSize ); UIWidget* addChatUI( LLMChat::Role role ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index dc8581f07..c810c3a5c 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -1450,7 +1450,22 @@ std::string AutoCompletePlugin::getPartialSymbol( TextDocument* doc ) { return doc->getText( { start, end } ).toUtf8(); } -void AutoCompletePlugin::update( UICodeEditor* ) { +void AutoCompletePlugin::update( UICodeEditor* editor ) { + const int iconSize = PixelDensity::dpToPxI( 12 ); + if ( mSuggestionIconDrawableSize != iconSize ) { + mSuggestionIconDrawables.clear(); + mSuggestionIconDrawableSize = iconSize; + } + for ( const auto& suggestion : mSuggestions ) { + const int iconKind = (int)suggestion.kind; + if ( mSuggestionIconDrawables.find( iconKind ) != mSuggestionIconDrawables.end() ) + continue; + UIIcon* icon = editor->getUISceneNode()->findIcon( + LSPCompletionItemHelper::toIconString( suggestion.kind ) ); + if ( icon ) + mSuggestionIconDrawables[iconKind] = icon->createDrawable( iconSize ); + } + for ( auto clientIt = mSnippetClients.begin(); clientIt != mSnippetClients.end(); ) { if ( !clientIt->second->isAttached() ) clientIt = mSnippetClients.erase( clientIt ); @@ -1702,8 +1717,8 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc text.draw( cursorPos.x + iconSpace.getWidth() + mBoxPadding.Left, cursorPos.y + mRowHeight * count + mBoxPadding.Top ); - Drawable* icon = editor->getUISceneNode()->findIconDrawable( - LSPCompletionItemHelper::toIconString( suggestion.kind ), PixelDensity::dpToPxI( 12 ) ); + auto iconIt = mSuggestionIconDrawables.find( (int)suggestion.kind ); + DrawablePtr icon = iconIt != mSuggestionIconDrawables.end() ? iconIt->second : DrawablePtr{}; if ( icon ) { Color iconColor( icon->getColor() ); @@ -2101,7 +2116,7 @@ bool AutoCompletePlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu->addSubMenu( i18n( "autocomplete", "Auto-Complete" ), mManager->getUISceneNode() ->findIcon( "symbol-string" ) - ->getSize( PixelDensity::dpToPxI( 12 ) ), + ->createDrawable( PixelDensity::dpToPxI( 12 ) ), subMenu ); return false; diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index 0ed35f94d..ed4e45f11 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -187,6 +187,8 @@ class AutoCompletePlugin : public Plugin { Float mRowHeight{ 0 }; Rectf mBoxRect; + UnorderedMap mSuggestionIconDrawables; + int mSuggestionIconDrawableSize{ 0 }; struct SnippetTabStopOccurrence { TextRange range; diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 81ec6b39a..15edcf137 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -183,6 +183,23 @@ DebuggerPlugin::~DebuggerPlugin() { } } +void DebuggerPlugin::update( UICodeEditor* editor ) { + if ( mBreakpointIcon == nullptr ) + mBreakpointIcon = getUISceneNode()->findIcon( "circle-perfect" ); + if ( mBreakpointIcon ) { + const int iconSize = (int)eefloor( editor->getLineHeight() * 0.875f ); + if ( mBreakpointDrawables.find( iconSize ) == mBreakpointDrawables.end() ) + mBreakpointDrawables[iconSize] = mBreakpointIcon->createDrawable( iconSize ); + } + if ( mStackFrameIcon == nullptr ) + mStackFrameIcon = getUISceneNode()->findIcon( "debug-stackframe" ); + if ( mStackFrameIcon ) { + const int iconSize = (int)eefloor( editor->getLineHeight() ); + if ( mStackFrameDrawables.find( iconSize ) == mStackFrameDrawables.end() ) + mStackFrameDrawables[iconSize] = mStackFrameIcon->createDrawable( iconSize ); + } +} + void DebuggerPlugin::onSaveProject( const std::string& /*projectFolder*/, const std::string& projectStatePath, bool rewriteStateOnlyIfNeeded ) { @@ -761,7 +778,7 @@ void DebuggerPlugin::buildSidePanelTab() { return; UIIcon* icon = findIcon( "debug" ); mTab = mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents, - icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); + icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "debugger_tab" ); mTab->setTextAsFallback( true ); @@ -821,7 +838,7 @@ void DebuggerPlugin::buildSidePanelTab() { mTabContents = getUISceneNode()->loadLayoutFromString( STYLE, nullptr, String::hash( "debugger_plugin" ) ); mTab = mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents, - icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); + icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "debugger_tab" ); mTab->setTextAsFallback( true ); @@ -1601,20 +1618,22 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, : SyntaxStyleTypes::LineNumber2 ) ) .blendAlpha( editor->getAlpha() ) ); - static UIIcon* circleFilled = getUISceneNode()->findIcon( "circle-perfect" ); - - if ( circleFilled ) { + bool iconDrawn = false; + if ( mBreakpointIcon ) { Float finalHeight = eefloor( radius * 1.75f ); - Drawable* drawable = circleFilled->getSize( finalHeight ); - if ( drawable ) { + auto drawableIt = mBreakpointDrawables.find( (int)finalHeight ); + if ( drawableIt != mBreakpointDrawables.end() && drawableIt->second ) { + DrawablePtr& drawable = drawableIt->second; Color oldColor = drawable->getColor(); drawable->setColor( color ); drawable->draw( Sizef{ lnPos.x, lnPos.y + ( lineHeight - finalHeight ) * 0.5f } .floor() ); drawable->setColor( oldColor ); + iconDrawn = true; } - } else { + } + if ( !iconDrawn ) { p.setColor( color ); p.drawCircle( Sizef{ lnPos.x + radius + ( gutterSpace - radius ) * 0.5f, @@ -1645,12 +1664,12 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, Float dim = radius * 2; Float gutterSpace = editor->getGutterSpace( this ); - static UIIcon* sfIcon = getUISceneNode()->findIcon( "debug-stackframe" ); - if ( sfIcon ) { - Drawable* drawable = sfIcon->getSize( lineHeight ); - if ( drawable ) { - drawable->setColor( color ); - drawable->draw( lnPos.floor() ); + if ( mStackFrameIcon ) { + const int iconSize = (int)eefloor( lineHeight ); + auto drawableIt = mStackFrameDrawables.find( iconSize ); + if ( drawableIt != mStackFrameDrawables.end() && drawableIt->second ) { + drawableIt->second->setColor( color ); + drawableIt->second->draw( lnPos.floor() ); return; } } diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index c805b26e6..605920f8d 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -138,6 +138,10 @@ class DebuggerPlugin : public PluginBase { std::string mCurDebugger; std::string mCurConfiguration; std::vector mRegisteredCommands; + UIIcon* mBreakpointIcon{ nullptr }; + UnorderedMap mBreakpointDrawables; + UIIcon* mStackFrameIcon{ nullptr }; + UnorderedMap mStackFrameDrawables; class DebuggerPluginClient : public TextDocument::Client { public: @@ -214,6 +218,8 @@ class DebuggerPlugin : public PluginBase { void onRegisterDocument( TextDocument* doc ) override; + void update( UICodeEditor* editor ) override; + void drawLineNumbersBefore( UICodeEditor* editor, const DocumentLineRange& lineRange, const Vector2f& startScroll, const Vector2f& screenStart, const Float& lineHeight, const Float& lineNumberWidth, diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 1401dcac0..ebab5a1d5 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -1303,7 +1303,7 @@ bool GitPlugin::onCreateContextMenu( UICodeEditor*, UIPopUpMenu* menu, const Vec const std::string& icon = "" ) { subMenu ->add( i18n( txtKey, txtVal ), - !icon.empty() ? findIcon( icon )->getSize( PixelDensity::dpToPxI( 12 ) ) + !icon.empty() ? findIcon( icon )->createDrawable( PixelDensity::dpToPxI( 12 ) ) : nullptr, KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) ->setId( txtKey ); @@ -1314,7 +1314,7 @@ bool GitPlugin::onCreateContextMenu( UICodeEditor*, UIPopUpMenu* menu, const Vec menu->addSubMenu( i18n( "git", "Git" ), mManager->getUISceneNode() ->findIcon( "source-control" ) - ->getSize( PixelDensity::dpToPxI( 12 ) ), + ->createDrawable( PixelDensity::dpToPxI( 12 ) ), subMenu ); return false; @@ -1453,7 +1453,7 @@ void GitPlugin::buildSidePanelTab() { return; UIIcon* icon = findIcon( "source-control" ); mTab = mSidePanel->add( i18n( "source_control", "Source Control" ), mTabContents, - icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); + icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "source_control_tab" ); mTab->setTextAsFallback( true ); return; @@ -1526,7 +1526,7 @@ void GitPlugin::buildSidePanelTab() { String::format( STYLE, color, color ), nullptr, String::hash( "git_plugin_style" ) ); mTab = mSidePanel->add( i18n( "source_control", "Source Control" ), mTabContents, - icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); + icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "source_control_tab" ); mTab->setTextAsFallback( true ); diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index e59d6bcfe..fa627e1e7 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -744,6 +744,15 @@ void LinterPlugin::onUnregister( UICodeEditor* editor ) { } void LinterPlugin::update( UICodeEditor* editor ) { + if ( mLightbulbIcon == nullptr ) + mLightbulbIcon = editor->getUISceneNode()->getUIIconThemeManager()->findIcon( + "lightbulb-autofix" ); + if ( mLightbulbIcon ) { + const int iconSize = (int)eefloor( editor->getLineHeight() ); + if ( mLightbulbDrawables.find( iconSize ) == mLightbulbDrawables.end() ) + mLightbulbDrawables[iconSize] = mLightbulbIcon->createDrawable( iconSize ); + } + std::shared_ptr doc = editor->getDocumentRef(); auto it = mDirtyDoc.find( doc.get() ); if ( it != mDirtyDoc.end() && it->second->getElapsedTime() >= mDelayTime ) { @@ -1144,14 +1153,12 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index, if ( !match.diagnostic.codeActions.empty() ) { Color wcolor( editor->getColorScheme().getEditorSyntaxStyle( "warning"_sst ).color ); - if ( nullptr == mLightbulbIcon ) { - mLightbulbIcon = editor->getUISceneNode()->getUIIconThemeManager()->findIcon( - "lightbulb-autofix" ); - } if ( nullptr != mLightbulbIcon ) { - Drawable* drawable = mLightbulbIcon->getSize( (int)eefloor( lineHeight ) ); - if ( drawable == nullptr ) + const int iconSize = (int)eefloor( lineHeight ); + auto drawableIt = mLightbulbDrawables.find( iconSize ); + if ( drawableIt == mLightbulbDrawables.end() || drawableIt->second == nullptr ) return; + DrawablePtr& drawable = drawableIt->second; Color oldColor( drawable->getColor() ); drawable->setColor( wcolor ); @@ -1501,7 +1508,7 @@ bool LinterPlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, match.lensBox[editor].contains( localPos ) ) { menu->addSeparator(); menu->add( editor->i18n( "linter_copy_error_message", "Copy Error Message" ), - mManager->getUISceneNode()->findIcon( "copy" )->getSize( + mManager->getUISceneNode()->findIcon( "copy" )->createDrawable( PixelDensity::dpToPxI( 12 ) ) ) ->setId( "linter-copy-error-message" ); mErrorMsg = match.text; diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index 7dd8df2f0..8275b0b53 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -157,6 +157,7 @@ class LinterPlugin : public Plugin { std::set mLSPLanguagesDisabled; String::HashType mConfigHash{ 0 }; UIIcon* mLightbulbIcon{ nullptr }; + UnorderedMap mLightbulbDrawables; std::string mErrorMsg; Rectf mQuickFixRect; std::string mOldMaxWidth; diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index c5f823711..130abf6d4 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -271,8 +271,42 @@ LSPClientPlugin::~LSPClientPlugin() { } } -void LSPClientPlugin::update( UICodeEditor* ) { +void LSPClientPlugin::update( UICodeEditor* editor ) { mClientManager.updateDirty(); + + if ( !mBreadcrumb ) + return; + Font* font = getUISceneNode()->getUIThemeManager()->getDefaultFont(); + if ( !font ) + return; + + const Float fontSize = getUISceneNode()->getUIThemeManager()->getDefaultFontSize(); + const int separatorSize = + PixelDensity::dpToPxI( font->getLineSpacing( fontSize ) * 0.5f ); + if ( mDrawSepIcon == nullptr ) + mDrawSepIcon = getUISceneNode()->findIcon( "chevron-right" ); + if ( mDrawSepIcon && + ( mDrawSepDrawable == nullptr || mDrawSepDrawableSize != separatorSize ) ) { + mDrawSepDrawable = mDrawSepIcon->createDrawable( separatorSize ); + mDrawSepDrawableSize = separatorSize; + } + + const int symbolIconSize = (int)fontSize; + if ( mBreadcrumbIconDrawableSize != symbolIconSize ) { + mBreadcrumbIconDrawables.clear(); + mBreadcrumbIconDrawableSize = symbolIconSize; + } + Lock l( mDocCurrentSymbolsMutex ); + auto symbolsIt = mDocCurrentSymbols.find( editor->getDocument().getURI() ); + if ( symbolsIt == mDocCurrentSymbols.end() ) + return; + for ( const auto& symbol : symbolsIt->second ) { + if ( mBreadcrumbIconDrawables.find( symbol.icon ) != mBreadcrumbIconDrawables.end() ) + continue; + UIIcon* icon = getUISceneNode()->findIcon( symbol.icon ); + if ( icon ) + mBreadcrumbIconDrawables[symbol.icon] = icon->createDrawable( symbolIconSize ); + } } struct LSPPositionAndServer { @@ -1963,33 +1997,29 @@ void LSPClientPlugin::drawTop( UICodeEditor* editor, const Vector2f& screenStart return; pos.x += drawn.getWidth(); - if ( mDrawSepIcon == nullptr ) - mDrawSepIcon = getUISceneNode()->findIcon( "chevron-right" ); Float textHeight = drawn.getHeight(); const auto& symbolsInfo = symbolsInfoIt->second; for ( const auto& info : symbolsInfo ) { - if ( mDrawSepIcon ) { + if ( mDrawSepDrawable ) { pos.x += eefloor( PixelDensity::dpToPx( 8 ) ); - Float iconSize = PixelDensity::dpToPxI( drawn.getHeight() * 0.5f ); - auto iconDrawable = mDrawSepIcon->getSize( iconSize ); - Color c = iconDrawable->getColor(); - iconDrawable->setColor( textColor ); - Float iconHeight = iconDrawable->getPixelsSize().getHeight(); + Color c = mDrawSepDrawable->getColor(); + mDrawSepDrawable->setColor( textColor ); + Float iconHeight = mDrawSepDrawable->getPixelsSize().getHeight(); Vector2f iconPos( { pos.x, screenStart.y + textOffsetY + eefloor( ( textHeight - iconHeight ) * 0.5f ) } ); - iconDrawable->draw( iconPos ); + mDrawSepDrawable->draw( iconPos ); pos.x += - iconDrawable->getPixelsSize().getWidth() + eefloor( PixelDensity::dpToPx( 8 ) ); - iconDrawable->setColor( c ); + mDrawSepDrawable->getPixelsSize().getWidth() + eefloor( PixelDensity::dpToPx( 8 ) ); + mDrawSepDrawable->setColor( c ); } else { pos.x += eefloor( PixelDensity::dpToPx( 16 ) ); } - UIIcon* iconKind = getUISceneNode()->findIcon( info.icon ); - if ( iconKind ) { - auto iconDrawable = iconKind->getSize( fontSize ); + auto iconIt = mBreadcrumbIconDrawables.find( info.icon ); + if ( iconIt != mBreadcrumbIconDrawables.end() && iconIt->second ) { + DrawablePtr& iconDrawable = iconIt->second; Color c = iconDrawable->getColor(); iconDrawable->setColor( textColor ); Float iconHeight = iconDrawable->getPixelsSize().getHeight(); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 4d8fac0c4..bb50c7780 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -149,6 +149,10 @@ class LSPClientPlugin : public Plugin { }; UnorderedMap> mDocCurrentSymbols; UIIcon* mDrawSepIcon{ nullptr }; + DrawablePtr mDrawSepDrawable; + int mDrawSepDrawableSize{ 0 }; + UnorderedMap mBreadcrumbIconDrawables; + int mBreadcrumbIconDrawableSize{ 0 }; std::string mConfigFileError; LSPClientPlugin( PluginManager* pluginManager, bool sync ); diff --git a/src/tools/ecode/plugins/plugin.cpp b/src/tools/ecode/plugins/plugin.cpp index 75b5c73f2..4c95998dc 100644 --- a/src/tools/ecode/plugins/plugin.cpp +++ b/src/tools/ecode/plugins/plugin.cpp @@ -64,9 +64,9 @@ UIIcon* Plugin::findIcon( const std::string& iconName ) { return getManager()->getUISceneNode()->findIcon( iconName ); } -Drawable* Plugin::iconDrawable( const std::string& iconName, Float dpSize ) { +DrawablePtr Plugin::iconDrawable( const std::string& iconName, Float dpSize ) { UIIcon* icon = findIcon( iconName ); - return icon ? icon->getSize( PixelDensity::dpToPx( dpSize ) ) : nullptr; + return icon ? icon->createDrawable( PixelDensity::dpToPx( dpSize ) ) : DrawablePtr{}; } void Plugin::showMessage( LSPMessageType type, const std::string& message, diff --git a/src/tools/ecode/plugins/plugin.hpp b/src/tools/ecode/plugins/plugin.hpp index 24a45b371..271166744 100644 --- a/src/tools/ecode/plugins/plugin.hpp +++ b/src/tools/ecode/plugins/plugin.hpp @@ -58,7 +58,7 @@ class Plugin : public UICodeEditorPlugin { UIIcon* findIcon( const std::string& iconName ); - Drawable* iconDrawable( const std::string& iconName, Float dpSize ); + DrawablePtr iconDrawable( const std::string& iconName, Float dpSize ); virtual void onVersionUpgrade( Uint32 /*oldVersion*/, Uint32 /*currentVersion*/ ) {} diff --git a/src/tools/ecode/plugins/plugincontextprovider.hpp b/src/tools/ecode/plugins/plugincontextprovider.hpp index 4bfd92c08..dfd1a9863 100644 --- a/src/tools/ecode/plugins/plugincontextprovider.hpp +++ b/src/tools/ecode/plugins/plugincontextprovider.hpp @@ -113,9 +113,9 @@ class PluginContextProvider { virtual ProjectDirectoryTree* getDirTree() const = 0; - virtual Drawable* findIcon( const std::string& name ) = 0; + virtual DrawablePtr findIcon( const std::string& name ) = 0; - virtual Drawable* findIcon( const std::string& name, const size_t iconSize ) = 0; + virtual DrawablePtr findIcon( const std::string& name, const size_t iconSize ) = 0; virtual TerminalConfig& termConfig() = 0; diff --git a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp index dccc38b29..75e307857 100644 --- a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp +++ b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp @@ -529,7 +529,7 @@ bool SpellCheckerPlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* const std::string& icon = "" ) { subMenu ->add( i18n( txtKey, txtVal ), - !icon.empty() ? findIcon( icon )->getSize( PixelDensity::dpToPxI( 12 ) ) + !icon.empty() ? findIcon( icon )->createDrawable( PixelDensity::dpToPxI( 12 ) ) : nullptr, KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) ->setId( txtKey ); diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index f24e63700..8232866e8 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -1231,7 +1231,7 @@ void ProjectBuildManager::buildSidePanelTab() { )html" ); mTab = mSidePanel->add( mUISceneNode->i18n( "build", "Build" ), node, - icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); + icon ? icon->createDrawable( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "build_tab" ); mTab->setTextAsFallback( true ); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 460245f41..8d83c5bd6 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -14,7 +14,7 @@ std::string SettingsMenu::getKeybind( const std::string& command ) { return mApp->getKeybind( command ); } -Drawable* SettingsMenu::findIcon( const std::string& name ) { +DrawablePtr SettingsMenu::findIcon( const std::string& name ) { return mApp->findIcon( name ); } diff --git a/src/tools/ecode/settingsmenu.hpp b/src/tools/ecode/settingsmenu.hpp index f6dd2c4b7..7b6b436f6 100644 --- a/src/tools/ecode/settingsmenu.hpp +++ b/src/tools/ecode/settingsmenu.hpp @@ -14,7 +14,7 @@ class SettingsMenu { std::string getKeybind( const std::string& command ); - Drawable* findIcon( const std::string& name ); + DrawablePtr findIcon( const std::string& name ); void runCommand( const std::string& command ); diff --git a/src/tools/ecode/statusterminalcontroller.cpp b/src/tools/ecode/statusterminalcontroller.cpp index 308780021..50c094b1a 100644 --- a/src/tools/ecode/statusterminalcontroller.cpp +++ b/src/tools/ecode/statusterminalcontroller.cpp @@ -242,7 +242,7 @@ UITerminal* StatusTerminalController::createTerminal( UIIcon* icon = mUISceneNode->findIcon( "terminal" ); auto tab = mTabWidget->add( - program, term, icon != nullptr ? icon->getSize( PixelDensity::dpToPxI( 12 ) ) : nullptr ); + program, term, icon != nullptr ? icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) : nullptr ); term->setData( (UintPtr)tab ); term->on( Event::OnTitleChange, [tab, term]( auto ) { tab->setText( term->getTitle() ); } ); diff --git a/src/tools/uieditor/uieditor.cpp b/src/tools/uieditor/uieditor.cpp index 9194c7f0e..7ee2c5e05 100644 --- a/src/tools/uieditor/uieditor.cpp +++ b/src/tools/uieditor/uieditor.cpp @@ -1111,7 +1111,7 @@ void App::fileMenuClick( const Event* event ) { SceneManager::instance()->setCurrentUISceneNode( mUISceneNode ); } -Drawable* App::findIcon( const std::string& icon ) { +DrawablePtr App::findIcon( const std::string& icon ) { return mAppUISceneNode->findIconDrawable( icon, mMenuIconSize ); } diff --git a/src/tools/uieditor/uieditor.hpp b/src/tools/uieditor/uieditor.hpp index cd8b289b9..8efd57710 100644 --- a/src/tools/uieditor/uieditor.hpp +++ b/src/tools/uieditor/uieditor.hpp @@ -207,7 +207,7 @@ class App : public UICodeEditorSplitter::Client { std::unordered_set mTmpDocs; ColorSchemeExtPreference mUIColorScheme; - Drawable* findIcon( const std::string& icon ); + DrawablePtr findIcon( const std::string& icon ); }; } // namespace uieditor From 2e7958c8d2d345306096c2557ea9e98891e983d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 20 Jul 2026 10:38:22 -0300 Subject: [PATCH 04/18] Compilation fixes. --- include/eepp/ui/uinodedrawable.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/eepp/ui/uinodedrawable.hpp b/include/eepp/ui/uinodedrawable.hpp index 64d1b8744..1ad5aa864 100644 --- a/include/eepp/ui/uinodedrawable.hpp +++ b/include/eepp/ui/uinodedrawable.hpp @@ -39,6 +39,8 @@ class EE_API UINodeDrawable : public Drawable { static LayerDrawable* New( UINodeDrawable* container ); LayerDrawable( UINodeDrawable* container ); + LayerDrawable( const LayerDrawable& ) = delete; + LayerDrawable& operator=( const LayerDrawable& ) = delete; virtual ~LayerDrawable(); @@ -160,6 +162,8 @@ class EE_API UINodeDrawable : public Drawable { static UINodeDrawable* New( UINode* owner ); UINodeDrawable( UINode* owner ); + UINodeDrawable( const UINodeDrawable& ) = delete; + UINodeDrawable& operator=( const UINodeDrawable& ) = delete; virtual ~UINodeDrawable(); From b5efd6d70942c40f0e8ec11761ee1501252e1489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Tue, 21 Jul 2026 21:16:32 -0300 Subject: [PATCH 05/18] Graphics: refine drawable cloning and icon source rendering: Rename Drawable::createInstance() to clone() and update drawable implementations, typed helpers, consumers, tests, tools, and external modules. Avoid retaining private icon clones for immediate single-threaded rendering in the code editor and ecode plugins. Borrow cached per-size icon sources instead, restoring temporary color changes after drawing, while preserving owned clones for widgets, menus, models, and other stateful consumers. Reduce common drawable allocation overhead by lazily creating DrawableResource callback state, using inline callback storage and notification snapshots, and safely supporting callback disconnection during notification. Lazily allocate UIImage and UINodeDrawable asynchronous lifetime tokens only when an uncached asynchronous load is actually scheduled. Document the distinction between borrowed immediate rendering and independently owned drawable state. --- .../resource_shared_ownership_architecture.md | 47 ++++++++------ include/eepp/graphics/arcdrawable.hpp | 2 +- include/eepp/graphics/circledrawable.hpp | 2 +- include/eepp/graphics/convexshapedrawable.hpp | 2 +- include/eepp/graphics/drawable.hpp | 2 +- include/eepp/graphics/drawablegroup.hpp | 2 +- include/eepp/graphics/drawableresource.hpp | 3 +- include/eepp/graphics/glyphdrawable.hpp | 2 +- include/eepp/graphics/ninepatch.hpp | 2 +- include/eepp/graphics/rectangledrawable.hpp | 2 +- include/eepp/graphics/sprite.hpp | 6 +- include/eepp/graphics/statelistdrawable.hpp | 2 +- include/eepp/graphics/texture.hpp | 2 +- include/eepp/graphics/texturedrawable.hpp | 2 +- include/eepp/graphics/textureregion.hpp | 2 +- include/eepp/graphics/triangledrawable.hpp | 2 +- include/eepp/ui/lineargradientdrawable.hpp | 2 +- include/eepp/ui/radialgradientdrawable.hpp | 2 +- include/eepp/ui/uicodeeditor.hpp | 2 - include/eepp/ui/uiskin.hpp | 4 +- src/eepp/graphics/arcdrawable.cpp | 2 +- src/eepp/graphics/circledrawable.cpp | 2 +- src/eepp/graphics/convexshapedrawable.cpp | 2 +- src/eepp/graphics/drawable.cpp | 2 +- src/eepp/graphics/drawablegroup.cpp | 4 +- src/eepp/graphics/drawableresource.cpp | 31 ++++++---- src/eepp/graphics/drawablesearcher.cpp | 20 +++--- src/eepp/graphics/glyphdrawable.cpp | 2 +- src/eepp/graphics/ninepatch.cpp | 4 +- src/eepp/graphics/rectangledrawable.cpp | 2 +- src/eepp/graphics/scrollparallax.cpp | 4 +- src/eepp/graphics/sprite.cpp | 10 +-- src/eepp/graphics/statelistdrawable.cpp | 4 +- src/eepp/graphics/texture.cpp | 2 +- src/eepp/graphics/texturedrawable.cpp | 2 +- src/eepp/graphics/textureregion.cpp | 2 +- src/eepp/graphics/triangledrawable.cpp | 2 +- src/eepp/ui/css/drawableimageparser.cpp | 2 +- src/eepp/ui/lineargradientdrawable.cpp | 2 +- src/eepp/ui/radialgradientdrawable.cpp | 2 +- src/eepp/ui/tools/uitabwidgetsplitter.cpp | 2 +- src/eepp/ui/uicodeeditor.cpp | 31 ++++------ src/eepp/ui/uiicon.cpp | 4 +- src/eepp/ui/uiimage.cpp | 7 ++- src/eepp/ui/uinode.cpp | 4 +- src/eepp/ui/uinodedrawable.cpp | 5 +- src/eepp/ui/uiskin.cpp | 8 +-- src/eepp/ui/uitheme.cpp | 12 ++-- .../src/eepp/maps/gameobjecttextureregion.cpp | 2 +- .../maps/src/eepp/maps/gameobjectvirtual.cpp | 2 +- src/tests/test_all/test.cpp | 2 +- .../resource_prerequisite_tests.cpp | 31 +++++++--- .../autocomplete/autocompleteplugin.cpp | 22 ++----- .../autocomplete/autocompleteplugin.hpp | 2 - .../ecode/plugins/debugger/debuggerplugin.cpp | 36 ++++------- .../ecode/plugins/debugger/debuggerplugin.hpp | 4 -- .../ecode/plugins/linter/linterplugin.cpp | 18 ++---- .../ecode/plugins/linter/linterplugin.hpp | 1 - .../ecode/plugins/lsp/lspclientplugin.cpp | 62 +++++-------------- .../ecode/plugins/lsp/lspclientplugin.hpp | 4 -- 60 files changed, 207 insertions(+), 247 deletions(-) diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index 3d75f9a27..397009c24 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -350,14 +350,14 @@ Resource resolution caches immutable source data. UI consumers own per-consumer ```cpp using DrawablePtr = ResourcePtr; -DrawablePtr Drawable::createInstance() const; +DrawablePtr Drawable::clone() const; DrawablePtr DrawableResolver::createDrawable( const DrawableRequest& request ); ``` Stage 4 established this contract without introducing a parallel `DrawableSource` class hierarchy. Existing drawable resource types serve as source prototypes while retained by an atlas, theme, icon, catalog, or resolver. A prototype is never handed directly to an unrelated consumer: -`createInstance()` returns independently mutable presentation state while sharing underlying +`clone()` returns independently mutable presentation state while sharing underlying texture/resource handles. This is simpler than duplicating every drawable type into source and instance classes and remains compatible with introducing immutable source-only types later when a concrete resource requires one. @@ -387,18 +387,21 @@ const DrawablePtr& UIIcon::getSource( int size ) const; DrawablePtr UIIcon::createDrawable( int size ) const; ``` -`getSource()` supports lookup and measurement without cloning an existing prototype. Glyph and SVG -icons may materialize and cache a missing size source once. `createDrawable()` is the explicit -consumer-instance boundary. Callers retain its result for as long as they render that icon. +`getSource()` supports lookup, measurement, and immediate rendering without cloning an existing +prototype. Glyph and SVG icons may materialize and cache a missing size source once. +`createDrawable()` is the explicit consumer-instance boundary for callers that retain the drawable +or need persistent independent state. -The migration will remove draw-time mutation of shared child/source objects. Rendering APIs may use -external draw parameters where that simplifies an implementation, but no shared source can be -temporarily recolored, resized, repositioned, or advanced by a consumer. +Immediate, single-threaded render paths may borrow an icon source and temporarily change +presentation state when they restore every changed value before returning and never retain the raw +pointer. Retained widget, menu, model, animated, or otherwise independently stateful consumers must +create and own an instance. Shared child mutation remains forbidden where drawing can be reentrant +or where the complete state cannot be restored locally. -No rendering callback may call `createInstance()`, `UIIcon::createDrawable()`, or an API that -performs either operation internally. Rendered instances are prepared during assignment, -deserialization, style/resource resolution, scheduled update, or plugin update and retained by the -consumer. The Stage 4 call-site audit classifies all remaining direct `createInstance()` calls as: +No rendering callback may call `clone()`, `UIIcon::createDrawable()`, or an API that performs either +operation internally. It must render either a previously retained instance or a borrowed source +under the temporary-state contract above. The Stage 4 call-site audit classifies all remaining +direct `clone()` calls as: - implementations recursively cloning their private child state; - constructors and setters adopting a private region/sprite/map instance; @@ -407,8 +410,8 @@ consumer. The Stage 4 call-site audit classifies all remaining direct `createIns - focused ownership tests. The code editor lock icon and ecode debugger, linter, LSP breadcrumb, and autocomplete icon paths -retain instances populated before drawing. Draw callbacks only look up and render those retained -instances; a cache miss skips the icon for that frame instead of cloning while rendering. +borrow their per-size icon sources at the point of immediate rendering and restore temporary color +changes before returning. Icons assigned to widgets, menus, or models still use owned instances. ### 7.2 Consumer API @@ -684,22 +687,28 @@ Exit criteria: Status: complete, 2026-07-20. Drawable ownership now uses `DrawablePtr`; textures create private -`TextureDrawable` wrappers; mutable prototypes implement `createInstance()`; sprites, state lists, +`TextureDrawable` wrappers; mutable prototypes implement `clone()`; sprites, state lists, skins, groups, nine-patches, regions, glyphs, gradients, and primitive drawables clone their presentation state. UIImage, UINodeDrawable, menus/icons/themes, parsers, editor/tool consumers, maps, physics, ecode, and eeiv were migrated in the same API cut. `DrawableResource::Unload` and callback IDs were replaced by Change-only RAII connections. +Callback state is allocated lazily on the first connection, so ordinary drawable resources carry +no callback allocation. Callback storage and notification snapshots use small inline buffers; +snapshotting preserves safe self-disconnection and reentrant mutation during notification without +allocating in the common case. `Variant` stores DrawablePtr outside its scalar union. UITextureRegion and ScrollParallax render with local geometry rather than temporarily resizing shared source regions; region-based map objects retain private instances. `DrawableSearcher` already returns fresh instances as a safe bridge, but its replacement by the layered UI resolver remains Stage 5. -`UIIcon::getSource()` now returns a cached source/prototype for lookup and measurement, while +`UIIcon::getSource()` now returns a cached source/prototype for lookup, measurement, and immediate +single-threaded rendering under the temporary-state restoration contract, while `UIIcon::createDrawable()` explicitly creates one private consumer instance. `UIGlyphIcon` and -`UISVGIcon` cache their lazily materialized sources under the same contract. The complete -`createInstance()` call-site audit found no remaining render-loop cloning; previously hot code -editor, debugger, linter, LSP breadcrumb, and autocomplete paths retain update-time instances. +`UISVGIcon` cache their lazily materialized sources under the same contract. The complete `clone()` +call-site audit found no remaining render-loop cloning. The code editor, debugger, linter, LSP +breadcrumb, and autocomplete draw-only paths borrow sources directly; retained widget and menu +icons continue to own instances. Introduce source types and per-consumer instances, remove shared draw-state mutation, replace manual ownership with DrawablePtr, remove Unload lifetime callbacks, add RAII change connections, and diff --git a/include/eepp/graphics/arcdrawable.hpp b/include/eepp/graphics/arcdrawable.hpp index 7b3d5b185..6d339b4c6 100644 --- a/include/eepp/graphics/arcdrawable.hpp +++ b/include/eepp/graphics/arcdrawable.hpp @@ -30,7 +30,7 @@ class EE_API ArcDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; Float getRadius() const; diff --git a/include/eepp/graphics/circledrawable.hpp b/include/eepp/graphics/circledrawable.hpp index 0f3523123..a2dca7fde 100644 --- a/include/eepp/graphics/circledrawable.hpp +++ b/include/eepp/graphics/circledrawable.hpp @@ -15,7 +15,7 @@ class EE_API CircleDrawable : public ArcDrawable { CircleDrawable( const Float& radius, const Uint32& segmentsCount ); - DrawablePtr createInstance() const; + DrawablePtr clone() const; }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/convexshapedrawable.hpp b/include/eepp/graphics/convexshapedrawable.hpp index cc5601c05..fc6c665f7 100644 --- a/include/eepp/graphics/convexshapedrawable.hpp +++ b/include/eepp/graphics/convexshapedrawable.hpp @@ -24,7 +24,7 @@ class EE_API ConvexShapeDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; void setPolygon( const Polygon2f& polygon ); diff --git a/include/eepp/graphics/drawable.hpp b/include/eepp/graphics/drawable.hpp index 5b33592ca..27c623166 100644 --- a/include/eepp/graphics/drawable.hpp +++ b/include/eepp/graphics/drawable.hpp @@ -65,7 +65,7 @@ class EE_API Drawable { /** Creates an independently mutable instance backed by the same immutable resource data. * This is an ownership/setup operation; rendering loops must retain and reuse the result. */ - virtual DrawablePtr createInstance() const; + virtual DrawablePtr clone() const; void setAlpha( Uint8 alpha ); diff --git a/include/eepp/graphics/drawablegroup.hpp b/include/eepp/graphics/drawablegroup.hpp index a214f762a..e1ba8c80c 100644 --- a/include/eepp/graphics/drawablegroup.hpp +++ b/include/eepp/graphics/drawablegroup.hpp @@ -26,7 +26,7 @@ class EE_API DrawableGroup : public Drawable { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; void clearDrawables(); diff --git a/include/eepp/graphics/drawableresource.hpp b/include/eepp/graphics/drawableresource.hpp index e0aabac64..9a2754284 100644 --- a/include/eepp/graphics/drawableresource.hpp +++ b/include/eepp/graphics/drawableresource.hpp @@ -2,6 +2,7 @@ #define EE_GRAPHICS_DRAWABLERESOURCE_HPP #include +#include #include #include @@ -12,7 +13,7 @@ class DrawableResource; struct DrawableResourceCallbackState { using Callback = std::function; Uint32 nextId{ 0 }; - UnorderedMap callbacks; + SmallVector, 2> callbacks; }; class EE_API DrawableResourceConnection { diff --git a/include/eepp/graphics/glyphdrawable.hpp b/include/eepp/graphics/glyphdrawable.hpp index 653f6d941..94f8efc4d 100644 --- a/include/eepp/graphics/glyphdrawable.hpp +++ b/include/eepp/graphics/glyphdrawable.hpp @@ -35,7 +35,7 @@ class EE_API GlyphDrawable : public DrawableResource { virtual bool isStateful(); - DrawablePtr createInstance() const; + DrawablePtr clone() const; /** @return The texture instance used by the GlyphDrawable. */ const TexturePtr& getTexture() const; diff --git a/include/eepp/graphics/ninepatch.hpp b/include/eepp/graphics/ninepatch.hpp index d2041fd12..71233271f 100644 --- a/include/eepp/graphics/ninepatch.hpp +++ b/include/eepp/graphics/ninepatch.hpp @@ -51,7 +51,7 @@ class EE_API NinePatch : public DrawableResource { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; TextureRegion* getTextureRegion( const int& side ); diff --git a/include/eepp/graphics/rectangledrawable.hpp b/include/eepp/graphics/rectangledrawable.hpp index 9f951082a..6ecae2dd5 100644 --- a/include/eepp/graphics/rectangledrawable.hpp +++ b/include/eepp/graphics/rectangledrawable.hpp @@ -28,7 +28,7 @@ class EE_API RectangleDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; Float getRotation() const; diff --git a/include/eepp/graphics/sprite.hpp b/include/eepp/graphics/sprite.hpp index 899f7a80a..471b7c507 100644 --- a/include/eepp/graphics/sprite.hpp +++ b/include/eepp/graphics/sprite.hpp @@ -317,7 +317,7 @@ class EE_API Sprite : public Drawable { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; /** Set the number of repetitions of the animation. Any number below 0 the animation will loop. */ @@ -386,8 +386,8 @@ class EE_API Sprite : public Drawable { /** Pop the event callback id indicated. */ bool popEventsCallback( const Uint32& callbackId ); - /** Creates an independent instance sharing the same texture resources. */ - SpritePtr clone() const; + /** Creates an independent sprite sharing the same texture resources. */ + SpritePtr cloneSprite() const; /** Update the sprite animation */ void update( const Time& ElapsedTime ); diff --git a/include/eepp/graphics/statelistdrawable.hpp b/include/eepp/graphics/statelistdrawable.hpp index 7912581de..c01bb266e 100644 --- a/include/eepp/graphics/statelistdrawable.hpp +++ b/include/eepp/graphics/statelistdrawable.hpp @@ -28,7 +28,7 @@ class EE_API StateListDrawable : public StatefulDrawable { virtual bool isStateful(); - DrawablePtr createInstance() const; + DrawablePtr clone() const; virtual StatefulDrawable* setState( Uint32 state ); diff --git a/include/eepp/graphics/texture.hpp b/include/eepp/graphics/texture.hpp index 4bc18c46d..e3395ee79 100644 --- a/include/eepp/graphics/texture.hpp +++ b/include/eepp/graphics/texture.hpp @@ -294,7 +294,7 @@ class EE_API Texture : public DrawableResource, public Image, private NonCopyabl virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; /** @return The process-wide identity assigned to this texture. */ ResourceId getTextureId() const; diff --git a/include/eepp/graphics/texturedrawable.hpp b/include/eepp/graphics/texturedrawable.hpp index 527ae3f13..8b6b994ea 100644 --- a/include/eepp/graphics/texturedrawable.hpp +++ b/include/eepp/graphics/texturedrawable.hpp @@ -22,7 +22,7 @@ class EE_API TextureDrawable : public DrawableResource { void draw( const Vector2f& position ); void draw( const Vector2f& position, const Sizef& size ); bool isStateful(); - DrawablePtr createInstance() const; + DrawablePtr clone() const; const TexturePtr& getTexture() const; diff --git a/include/eepp/graphics/textureregion.hpp b/include/eepp/graphics/textureregion.hpp index 6342b4d79..4d13158a7 100644 --- a/include/eepp/graphics/textureregion.hpp +++ b/include/eepp/graphics/textureregion.hpp @@ -129,7 +129,7 @@ class EE_API TextureRegion : public DrawableResource { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; /** @return The texture instance used by the TextureRegion. */ const TexturePtr& getTexture() const; diff --git a/include/eepp/graphics/triangledrawable.hpp b/include/eepp/graphics/triangledrawable.hpp index c5b1c67e9..625fc68d0 100644 --- a/include/eepp/graphics/triangledrawable.hpp +++ b/include/eepp/graphics/triangledrawable.hpp @@ -28,7 +28,7 @@ class EE_API TriangleDrawable : public PrimitiveDrawable { virtual bool isStateful() { return false; } - DrawablePtr createInstance() const; + DrawablePtr clone() const; void setSize( const Sizef& size ); diff --git a/include/eepp/ui/lineargradientdrawable.hpp b/include/eepp/ui/lineargradientdrawable.hpp index 4b28292d5..e4fe8e1e7 100644 --- a/include/eepp/ui/lineargradientdrawable.hpp +++ b/include/eepp/ui/lineargradientdrawable.hpp @@ -50,7 +50,7 @@ class EE_API LinearGradientDrawable : public Graphics::Drawable { virtual bool isStateful() { return false; } - Graphics::DrawablePtr createInstance() const; + Graphics::DrawablePtr clone() const; const std::vector& getColorStops() const; diff --git a/include/eepp/ui/radialgradientdrawable.hpp b/include/eepp/ui/radialgradientdrawable.hpp index 626a92f7c..1161f1753 100644 --- a/include/eepp/ui/radialgradientdrawable.hpp +++ b/include/eepp/ui/radialgradientdrawable.hpp @@ -54,7 +54,7 @@ class EE_API RadialGradientDrawable : public Graphics::Drawable { virtual bool isStateful() { return false; } - Graphics::DrawablePtr createInstance() const; + Graphics::DrawablePtr clone() const; const std::vector& getColorStops() const; diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 6529e7512..c59a66c69 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -997,8 +997,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Text mLineTextCache; size_t mJumpLinesLength{ 5 }; UIIcon* mFileLockIcon{ nullptr }; - DrawablePtr mFileLockDrawable; - int mFileLockDrawableSize{ 0 }; std::string mFileLockIconName{ "file-lock-fill" }; LineWrapType mLineWrapType{ LineWrapType::Viewport }; DrawablePtr mFoldDrawable; diff --git a/include/eepp/ui/uiskin.hpp b/include/eepp/ui/uiskin.hpp index 8b10d2844..3ab26de28 100644 --- a/include/eepp/ui/uiskin.hpp +++ b/include/eepp/ui/uiskin.hpp @@ -20,9 +20,9 @@ class EE_API UISkin : public StateListDrawable { virtual Sizef getPixelsSize(); - DrawablePtr createInstance() const; + DrawablePtr clone() const; - ResourcePtr clone() const; + ResourcePtr cloneSkin() const; ResourcePtr clone( const std::string& newName ) const; diff --git a/src/eepp/graphics/arcdrawable.cpp b/src/eepp/graphics/arcdrawable.cpp index 8577151ab..959435d14 100644 --- a/src/eepp/graphics/arcdrawable.cpp +++ b/src/eepp/graphics/arcdrawable.cpp @@ -31,7 +31,7 @@ ArcDrawable::ArcDrawable( const Float& radius, Uint32 segmentsCount, const Float mSegmentsCount = mSegmentsCount > 360 ? 360 : mSegmentsCount; } -DrawablePtr ArcDrawable::createInstance() const { +DrawablePtr ArcDrawable::clone() const { auto instance = makeResource( mRadius, mSegmentsCount, mArcAngle, mArcStartAngle ); instance->mOffset = mOffset; instance->mFillMode = mFillMode; diff --git a/src/eepp/graphics/circledrawable.cpp b/src/eepp/graphics/circledrawable.cpp index 78033d7eb..575f2323c 100644 --- a/src/eepp/graphics/circledrawable.cpp +++ b/src/eepp/graphics/circledrawable.cpp @@ -15,7 +15,7 @@ CircleDrawable::CircleDrawable() : ArcDrawable( 0, 64 ) {} CircleDrawable::CircleDrawable( const Float& radius, const Uint32& segmentsCount ) : ArcDrawable( radius, segmentsCount ) {} -DrawablePtr CircleDrawable::createInstance() const { +DrawablePtr CircleDrawable::clone() const { auto instance = makeResource( mRadius, mSegmentsCount ); instance->mArcAngle = mArcAngle; instance->mArcStartAngle = mArcStartAngle; diff --git a/src/eepp/graphics/convexshapedrawable.cpp b/src/eepp/graphics/convexshapedrawable.cpp index 5bd602ab7..2f1d3d0fd 100644 --- a/src/eepp/graphics/convexshapedrawable.cpp +++ b/src/eepp/graphics/convexshapedrawable.cpp @@ -9,7 +9,7 @@ ConvexShapeDrawable* ConvexShapeDrawable::New() { ConvexShapeDrawable::ConvexShapeDrawable() : PrimitiveDrawable( Drawable::CONVEXSHAPE ) {} -DrawablePtr ConvexShapeDrawable::createInstance() const { +DrawablePtr ConvexShapeDrawable::clone() const { auto instance = makeResource(); instance->mPolygon = mPolygon; instance->mIndexColor = mIndexColor; diff --git a/src/eepp/graphics/drawable.cpp b/src/eepp/graphics/drawable.cpp index 5f1ae516f..fce244bbf 100644 --- a/src/eepp/graphics/drawable.cpp +++ b/src/eepp/graphics/drawable.cpp @@ -9,7 +9,7 @@ Drawable::Drawable( Type drawableType ) : Drawable::~Drawable() {} -DrawablePtr Drawable::createInstance() const { +DrawablePtr Drawable::clone() const { return {}; } diff --git a/src/eepp/graphics/drawablegroup.cpp b/src/eepp/graphics/drawablegroup.cpp index 6e0bf1e1b..93ae78534 100644 --- a/src/eepp/graphics/drawablegroup.cpp +++ b/src/eepp/graphics/drawablegroup.cpp @@ -15,7 +15,7 @@ DrawableGroup::~DrawableGroup() { clearDrawables(); } -DrawablePtr DrawableGroup::createInstance() const { +DrawablePtr DrawableGroup::clone() const { auto instance = makeResource(); instance->mPosition = mPosition; instance->mColor = mColor; @@ -25,7 +25,7 @@ DrawablePtr DrawableGroup::createInstance() const { for ( const auto& drawable : mGroup ) { if ( !drawable ) continue; - DrawablePtr drawableInstance = drawable->createInstance(); + DrawablePtr drawableInstance = drawable->clone(); if ( !drawableInstance ) return {}; instance->addDrawable( std::move( drawableInstance ) ); diff --git a/src/eepp/graphics/drawableresource.cpp b/src/eepp/graphics/drawableresource.cpp index 6d433a12d..cc7261a99 100644 --- a/src/eepp/graphics/drawableresource.cpp +++ b/src/eepp/graphics/drawableresource.cpp @@ -1,5 +1,7 @@ #include +#include + namespace EE { namespace Graphics { DrawableResourceConnection::DrawableResourceConnection( @@ -29,8 +31,13 @@ DrawableResourceConnection::operator=( DrawableResourceConnection&& other ) noex void DrawableResourceConnection::disconnect() { if ( mId != 0 ) { - if ( auto state = mState.lock() ) - state->callbacks.erase( mId ); + if ( auto state = mState.lock() ) { + auto callback = + std::find_if( state->callbacks.begin(), state->callbacks.end(), + [this]( const auto& callback ) { return callback.first == mId; } ); + if ( callback != state->callbacks.end() ) + state->callbacks.erase( callback ); + } } mState.reset(); mId = 0; @@ -40,17 +47,12 @@ DrawableResourceConnection::operator bool() const { return mId != 0 && !mState.expired(); } -DrawableResource::DrawableResource( Type drawableType ) : - Drawable( drawableType ), - mId( 0 ), - mCallbackState( std::make_shared() ) { +DrawableResource::DrawableResource( Type drawableType ) : Drawable( drawableType ), mId( 0 ) { createUnnamed(); } DrawableResource::DrawableResource( Type drawableType, const std::string& name ) : - Drawable( drawableType ), - mId( 0 ), - mCallbackState( std::make_shared() ) { + Drawable( drawableType ), mId( 0 ) { setName( name ); } @@ -83,8 +85,10 @@ void DrawableResource::onResourceChange() { } void DrawableResource::sendResourceChanged() { - std::vector callbacks; - callbacks.reserve( mCallbackState->callbacks.size() ); + if ( !mCallbackState ) + return; + + SmallVector callbacks; for ( const auto& callback : mCallbackState->callbacks ) callbacks.emplace_back( callback.second ); for ( const auto& callback : callbacks ) @@ -93,8 +97,11 @@ void DrawableResource::sendResourceChanged() { DrawableResourceConnection DrawableResource::connectResourceChange( OnResourceChangeCallback callback ) { + if ( !mCallbackState ) + mCallbackState = std::make_shared(); + Uint32 id = ++mCallbackState->nextId; - mCallbackState->callbacks.emplace( id, std::move( callback ) ); + mCallbackState->callbacks.emplace_back( id, std::move( callback ) ); return DrawableResourceConnection( mCallbackState, id ); } diff --git a/src/eepp/graphics/drawablesearcher.cpp b/src/eepp/graphics/drawablesearcher.cpp index 74986327a..6207820c6 100644 --- a/src/eepp/graphics/drawablesearcher.cpp +++ b/src/eepp/graphics/drawablesearcher.cpp @@ -43,11 +43,11 @@ static DrawablePtr searchByNameInternal( const std::string& name, ResourceScope& } if ( source ) { - return source->createInstance(); + return source->clone(); } TexturePtr texture = resourceScope.findTexture( name ); - return texture ? texture->createInstance() : DrawablePtr{}; + return texture ? texture->clone() : DrawablePtr{}; } static DrawablePtr parseDataURI( const std::string& name, ResourceScope& scope ) { @@ -106,7 +106,7 @@ static DrawablePtr parseDataURI( const std::string& name, ResourceScope& scope ) texture = std::move( tex ); } } - return texture ? texture->createInstance() : DrawablePtr{}; + return texture ? texture->clone() : DrawablePtr{}; } DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstSearchSprite, @@ -137,20 +137,20 @@ DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstS if ( String::startsWith( name, "@textureregion/" ) ) { if ( Drawable* source = TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ) ) - drawable = source->createInstance(); + drawable = source->clone(); } else if ( String::startsWith( name, "@image/" ) ) { TexturePtr texture = resourceScope.findTexture( name.substr( 7 ) ); - drawable = texture ? texture->createInstance() : DrawablePtr{}; + drawable = texture ? texture->clone() : DrawablePtr{}; } else if ( String::startsWith( name, "@texture/" ) ) { TexturePtr texture = resourceScope.findTexture( name.substr( 9 ) ); - drawable = texture ? texture->createInstance() : DrawablePtr{}; + drawable = texture ? texture->clone() : DrawablePtr{}; } else if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) { drawable = getSprite( name.substr( 8 ) ); } else if ( String::startsWith( name, "@drawable/" ) ) { drawable = searchByNameInternal( name.substr( 10 ), resourceScope ); } else if ( String::startsWith( name, "@9p/" ) ) { if ( Drawable* source = NinePatchManager::instance()->getByName( name.substr( 4 ) ) ) - drawable = source->createInstance(); + drawable = source->clone(); } else { drawable = searchByNameInternal( name, resourceScope ); } @@ -176,7 +176,7 @@ DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstS texture = std::move( tex ); } } - drawable = texture ? texture->createInstance() : DrawablePtr{}; + drawable = texture ? texture->clone() : DrawablePtr{}; } else if ( String::startsWith( name, "http://" ) || String::startsWith( name, "https://" ) ) { TexturePtr texture = resourceScope.findTexture( name ); @@ -209,7 +209,7 @@ DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstS URI( name ), Seconds( 5 ), {}, headers ); } - drawable = texture ? texture->createInstance() : DrawablePtr{}; + drawable = texture ? texture->clone() : DrawablePtr{}; } else if ( String::startsWith( name, "data:image/" ) ) { drawable = parseDataURI( name, resourceScope ); } else { @@ -225,7 +225,7 @@ DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstS DrawablePtr DrawableSearcher::searchById( const Uint32& id ) { Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); - DrawablePtr drawable = source ? source->createInstance() : DrawablePtr{}; + DrawablePtr drawable = source ? source->clone() : DrawablePtr{}; if ( !drawable && sPrintWarnings ) Log::warning( "DrawableSearcher::searchById: \"%ld\" not found", id ); diff --git a/src/eepp/graphics/glyphdrawable.cpp b/src/eepp/graphics/glyphdrawable.cpp index 0d26c68f5..1ddea05de 100644 --- a/src/eepp/graphics/glyphdrawable.cpp +++ b/src/eepp/graphics/glyphdrawable.cpp @@ -80,7 +80,7 @@ bool GlyphDrawable::isStateful() { return false; } -DrawablePtr GlyphDrawable::createInstance() const { +DrawablePtr GlyphDrawable::clone() const { auto instance = makeResource( mTexture, mSrcRect.asInt(), mDestSize, mName ); instance->setPixelDensity( mPixelDensity ); instance->setGlyphOffset( mGlyphOffset ); diff --git a/src/eepp/graphics/ninepatch.cpp b/src/eepp/graphics/ninepatch.cpp index 05c3d5e63..d2a12a3b7 100644 --- a/src/eepp/graphics/ninepatch.cpp +++ b/src/eepp/graphics/ninepatch.cpp @@ -65,14 +65,14 @@ NinePatch::NinePatch( TextureRegion* textureRegion, int left, int top, int right NinePatch::~NinePatch() {} -DrawablePtr NinePatch::createInstance() const { +DrawablePtr NinePatch::clone() const { if ( !mDrawable[Center] ) return {}; auto instance = makeResource( mDrawable[Center]->getTexture(), mRect.Left, mRect.Top, mRect.Right, mRect.Bottom, mPixelDensity, mName ); for ( int i = 0; i < SideCount; ++i ) { instance->mDrawable[i] = - mDrawable[i] ? std::static_pointer_cast( mDrawable[i]->createInstance() ) + mDrawable[i] ? std::static_pointer_cast( mDrawable[i]->clone() ) : TextureRegionPtr{}; if ( mDrawable[i] && !instance->mDrawable[i] ) return {}; diff --git a/src/eepp/graphics/rectangledrawable.cpp b/src/eepp/graphics/rectangledrawable.cpp index fc48542e7..8ea6e6e03 100644 --- a/src/eepp/graphics/rectangledrawable.cpp +++ b/src/eepp/graphics/rectangledrawable.cpp @@ -29,7 +29,7 @@ RectangleDrawable::RectangleDrawable( const Vector2f& position, const Sizef& siz mPosition = position; } -DrawablePtr RectangleDrawable::createInstance() const { +DrawablePtr RectangleDrawable::clone() const { auto instance = makeResource( mPosition, mSize ); instance->mRotation = mRotation; instance->mScale = mScale; diff --git a/src/eepp/graphics/scrollparallax.cpp b/src/eepp/graphics/scrollparallax.cpp index fd633b2b3..3535b80a9 100644 --- a/src/eepp/graphics/scrollparallax.cpp +++ b/src/eepp/graphics/scrollparallax.cpp @@ -20,7 +20,7 @@ TextureRegion* ScrollParallax::getTextureRegion() const { void ScrollParallax::setTextureRegion( TextureRegion* textureRegion ) { mTextureRegion = - textureRegion ? std::static_pointer_cast( textureRegion->createInstance() ) + textureRegion ? std::static_pointer_cast( textureRegion->clone() ) : TextureRegionPtr{}; setTextureRegion(); @@ -46,7 +46,7 @@ bool ScrollParallax::create( TextureRegion* textureRegion, const Vector2f& Posit const Sizef& Size, const Vector2f& Speed, const Color& Color, const BlendMode& Blend ) { mTextureRegion = - textureRegion ? std::static_pointer_cast( textureRegion->createInstance() ) + textureRegion ? std::static_pointer_cast( textureRegion->clone() ) : TextureRegionPtr{}; mPos = Position; mSize = Size; diff --git a/src/eepp/graphics/sprite.cpp b/src/eepp/graphics/sprite.cpp index 42bc69a60..9f0bebf4b 100644 --- a/src/eepp/graphics/sprite.cpp +++ b/src/eepp/graphics/sprite.cpp @@ -77,7 +77,7 @@ Sprite& Sprite::operator=( const Sprite& Other ) { frame.Spr.reserve( otherFrame.Spr.size() ); for ( const TextureRegionPtr& region : otherFrame.Spr ) { frame.Spr.emplace_back( - region ? std::static_pointer_cast( region->createInstance() ) + region ? std::static_pointer_cast( region->clone() ) : nullptr ); } mFrames.emplace_back( std::move( frame ) ); @@ -115,12 +115,12 @@ Sprite& Sprite::operator=( const Sprite& Other ) { return *this; } -SpritePtr Sprite::clone() const { +SpritePtr Sprite::cloneSprite() const { return makeResource( *this ); } -DrawablePtr Sprite::createInstance() const { - return clone(); +DrawablePtr Sprite::clone() const { + return cloneSprite(); } void Sprite::clearFrame() { @@ -381,7 +381,7 @@ bool Sprite::addFramesByPattern( const std::string& name, const std::string& ext bool Sprite::addSubFrame( TextureRegion* TextureRegion, const unsigned int& NumFrame, const unsigned int& NumSubFrame ) { return addSubFrame( TextureRegion ? std::static_pointer_cast( - TextureRegion->createInstance() ) + TextureRegion->clone() ) : TextureRegionPtr{}, NumFrame, NumSubFrame ); } diff --git a/src/eepp/graphics/statelistdrawable.cpp b/src/eepp/graphics/statelistdrawable.cpp index 18ad874df..11824d8e9 100644 --- a/src/eepp/graphics/statelistdrawable.cpp +++ b/src/eepp/graphics/statelistdrawable.cpp @@ -20,14 +20,14 @@ void StateListDrawable::clearDrawables() { mDrawables.clear(); } -DrawablePtr StateListDrawable::createInstance() const { +DrawablePtr StateListDrawable::clone() const { auto instance = ResourcePtr( eeNew( StateListDrawable, ( mName ) ), ResourceDeleter() ); instance->setColor( mColor ); instance->setPosition( mPosition ); for ( const auto& state : mDrawables ) { if ( state.second ) { - DrawablePtr drawable = state.second->createInstance(); + DrawablePtr drawable = state.second->clone(); if ( !drawable ) return {}; instance->setStateDrawable( state.first, std::move( drawable ) ); diff --git a/src/eepp/graphics/texture.cpp b/src/eepp/graphics/texture.cpp index a4bead9dc..9c5dc43a0 100644 --- a/src/eepp/graphics/texture.cpp +++ b/src/eepp/graphics/texture.cpp @@ -18,7 +18,7 @@ using namespace EE::Graphics::Private; namespace EE { namespace Graphics { -DrawablePtr Texture::createInstance() const { +DrawablePtr Texture::clone() const { TexturePtr texture = TextureFactory::instance()->getTexture( getTextureId() ); if ( !texture ) return {}; diff --git a/src/eepp/graphics/texturedrawable.cpp b/src/eepp/graphics/texturedrawable.cpp index ea7d19d56..ef8dbcd5d 100644 --- a/src/eepp/graphics/texturedrawable.cpp +++ b/src/eepp/graphics/texturedrawable.cpp @@ -41,7 +41,7 @@ bool TextureDrawable::isStateful() { return false; } -DrawablePtr TextureDrawable::createInstance() const { +DrawablePtr TextureDrawable::clone() const { TextureDrawablePtr instance = New( mTexture ); instance->setColor( mColor ); instance->setPosition( mPosition ); diff --git a/src/eepp/graphics/textureregion.cpp b/src/eepp/graphics/textureregion.cpp index b8f372d45..cee22e335 100644 --- a/src/eepp/graphics/textureregion.cpp +++ b/src/eepp/graphics/textureregion.cpp @@ -117,7 +117,7 @@ TextureRegion::~TextureRegion() { clearCache(); } -DrawablePtr TextureRegion::createInstance() const { +DrawablePtr TextureRegion::clone() const { auto instance = makeResource( mTexture, mSrcRect, mDestSize, mOffset, mName ); instance->setOriDestSize( mOriDestSize ); instance->setPixelDensity( mPixelDensity ); diff --git a/src/eepp/graphics/triangledrawable.cpp b/src/eepp/graphics/triangledrawable.cpp index 896acb2ad..1c77650f0 100644 --- a/src/eepp/graphics/triangledrawable.cpp +++ b/src/eepp/graphics/triangledrawable.cpp @@ -18,7 +18,7 @@ TriangleDrawable::TriangleDrawable( const Vector2f& position, const Sizef& size mPosition = position; } -DrawablePtr TriangleDrawable::createInstance() const { +DrawablePtr TriangleDrawable::clone() const { auto instance = makeResource( mPosition, mSize ); instance->mTriangle = mTriangle; instance->mComputedTriangle = mComputedTriangle; diff --git a/src/eepp/ui/css/drawableimageparser.cpp b/src/eepp/ui/css/drawableimageparser.cpp index 856fc289a..28ce99ebb 100644 --- a/src/eepp/ui/css/drawableimageparser.cpp +++ b/src/eepp/ui/css/drawableimageparser.cpp @@ -966,7 +966,7 @@ void DrawableImageParser::registerBaseParsers() { } Drawable* drawable = font->getGlyphDrawable( codePoint, node->convertLength( params[1], size.getWidth() ) ); - return drawable ? drawable->createInstance() : DrawablePtr{}; + return drawable ? drawable->clone() : DrawablePtr{}; }; } diff --git a/src/eepp/ui/lineargradientdrawable.cpp b/src/eepp/ui/lineargradientdrawable.cpp index 2340d8f57..268ebceb9 100644 --- a/src/eepp/ui/lineargradientdrawable.cpp +++ b/src/eepp/ui/lineargradientdrawable.cpp @@ -18,7 +18,7 @@ LinearGradientDrawable* LinearGradientDrawable::NewRepeating() { LinearGradientDrawable::LinearGradientDrawable( Graphics::Drawable::Type drawableType ) : Drawable( drawableType ) {} -DrawablePtr LinearGradientDrawable::createInstance() const { +DrawablePtr LinearGradientDrawable::clone() const { auto instance = makeResource( mDrawableType ); instance->mColorStops = mColorStops; instance->mAngle = mAngle; diff --git a/src/eepp/ui/radialgradientdrawable.cpp b/src/eepp/ui/radialgradientdrawable.cpp index 05a98c363..a9b80792b 100644 --- a/src/eepp/ui/radialgradientdrawable.cpp +++ b/src/eepp/ui/radialgradientdrawable.cpp @@ -18,7 +18,7 @@ RadialGradientDrawable* RadialGradientDrawable::NewRepeating() { RadialGradientDrawable::RadialGradientDrawable( Graphics::Drawable::Type drawableType ) : Drawable( drawableType ) {} -DrawablePtr RadialGradientDrawable::createInstance() const { +DrawablePtr RadialGradientDrawable::clone() const { auto instance = makeResource( mDrawableType ); instance->mColorStops = mColorStops; instance->mShape = mShape; diff --git a/src/eepp/ui/tools/uitabwidgetsplitter.cpp b/src/eepp/ui/tools/uitabwidgetsplitter.cpp index b5b6df8bf..7fc460016 100644 --- a/src/eepp/ui/tools/uitabwidgetsplitter.cpp +++ b/src/eepp/ui/tools/uitabwidgetsplitter.cpp @@ -912,7 +912,7 @@ void UITabWidgetSplitter::unserializeNode( const nlohmann::json& j, UITabWidget* !result.title.empty() ? result.title : file.value( "title", "" ); auto [tab, _] = createWidgetInTabWidget( curTabWidget, result.widget, title ); if ( result.icon ) - tab->setIcon( result.icon->createInstance() ); + tab->setIcon( result.icon->clone() ); } } if ( curTabWidget->getTabCount() > 0 ) { diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 7c8cd3f98..a1ab4161f 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -484,17 +484,6 @@ void UICodeEditor::scheduledUpdate( const Time& ) { if ( !mVisible ) return; - if ( mLocked && mDisplayLockedIcon ) { - if ( mFileLockIcon == nullptr && !mFileLockIconName.empty() ) - mFileLockIcon = getUISceneNode()->findIcon( mFileLockIconName ); - const int iconSize = PixelDensity::dpToPxI( 16 ); - if ( mFileLockIcon && - ( mFileLockDrawable == nullptr || mFileLockDrawableSize != iconSize ) ) { - mFileLockDrawable = mFileLockIcon->createDrawable( iconSize ); - mFileLockDrawableSize = iconSize; - } - } - if ( mDoc && !mDoc->isLoading() && !mDoc->isEmpty() && !mDoc->getSyntaxDefinition().getPatterns().empty() && mDoc->getHighlighter()->updateDirty( getVisibleLinesCount() ) ) { @@ -1157,21 +1146,27 @@ void UICodeEditor::updateIMELocation() { } void UICodeEditor::drawLockedIcon( const Vector2f start ) { - if ( mFileLockDrawable == nullptr ) + if ( mFileLockIcon == nullptr && !mFileLockIconName.empty() ) + mFileLockIcon = getUISceneNode()->findIcon( mFileLockIconName ); + if ( mFileLockIcon == nullptr ) return; - Float w = mFileLockDrawable->getPixelsSize().getWidth(); + Drawable* fileLockIcon = mFileLockIcon->getSource( PixelDensity::dpToPxI( 16 ) ).get(); + if ( fileLockIcon == nullptr ) + return; + + Float w = fileLockIcon->getPixelsSize().getWidth(); Float posX = mMinimapEnabled ? getMinimapRect( getScreenStart() ).Left - w : ( start.x + mSize.getWidth() - ( mVScrollBar->isVisible() ? mVScrollBar->getPixelsSize().getWidth() : 0 ) ) - mPadding.Right - w; - Color col( mFileLockDrawable->getColor() ); - mFileLockDrawable->setColor( Color( mFontStyleConfig.getFontColor() ).blendAlpha( mAlpha ) ); + Color col( fileLockIcon->getColor() ); + fileLockIcon->setColor( Color( mFontStyleConfig.getFontColor() ).blendAlpha( mAlpha ) ); Float margin = PixelDensity::dpToPxI( 4 ); - mFileLockDrawable->draw( { posX - margin, start.y + margin } ); - mFileLockDrawable->setColor( col ); + fileLockIcon->draw( { posX - margin, start.y + margin } ); + fileLockIcon->setColor( col ); } size_t UICodeEditor::getTotalVisibleLines() const { @@ -3758,8 +3753,6 @@ void UICodeEditor::setFileLockIconName( const std::string& fileLockIconName ) { if ( mFileLockIconName != fileLockIconName ) { mFileLockIconName = fileLockIconName; mFileLockIcon = nullptr; - mFileLockDrawable.reset(); - mFileLockDrawableSize = 0; } } diff --git a/src/eepp/ui/uiicon.cpp b/src/eepp/ui/uiicon.cpp index 323929178..b3c03d905 100644 --- a/src/eepp/ui/uiicon.cpp +++ b/src/eepp/ui/uiicon.cpp @@ -36,7 +36,7 @@ const DrawablePtr& UIIcon::getSource( const int& size ) const { DrawablePtr UIIcon::createDrawable( const int& size ) const { const DrawablePtr& source = getSource( size ); - return source ? source->createInstance() : DrawablePtr{}; + return source ? source->clone() : DrawablePtr{}; } void UIIcon::setSource( const int& size, DrawablePtr drawable ) { @@ -57,7 +57,7 @@ const DrawablePtr& UIGlyphIcon::getSource( const int& size ) const { GlyphDrawable* drawable = mFont->getGlyphDrawable( mCodePoint, size ); if ( !drawable ) return empty; - const_cast( this )->setSource( size, drawable->createInstance() ); + const_cast( this )->setSource( size, drawable->clone() ); return UIIcon::getSource( size ); } diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 0d5b3b02d..2b6baef26 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -68,8 +68,7 @@ UIImage::UIImage( const std::string& tag ) : UIWidget( tag ), mScaleType( UIScaleType::None ), mColor(), - mAlignOffset( 0, 0 ), - mAsyncImageAlive( std::make_shared>( true ) ) { + mAlignOffset( 0, 0 ) { mFlags |= UI_AUTO_SIZE; applyDefaultTheme(); @@ -347,6 +346,8 @@ bool UIImage::loadFileDrawable( const Network::URI& uri ) { auto resourceState = scene->getAsyncResourceLoadState(); Uint64 resourceGeneration = resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; + if ( !mAsyncImageAlive ) + mAsyncImageAlive = std::make_shared>( true ); auto alive = mAsyncImageAlive; scene->getThreadPool()->run( [resourceState, resourceGeneration, resourceScope, alive, loadId, @@ -396,6 +397,8 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { Uint64 resourceGeneration = resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; Uint64 loadId = ++mRemoteImageLoadId; + if ( !mAsyncImageAlive ) + mAsyncImageAlive = std::make_shared>( true ); auto alive = mAsyncImageAlive; TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index a8861f8cb..8fe08d863 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -1253,7 +1253,7 @@ UINode* UINode::setThemeSkin( UITheme* Theme, const std::string& skinName ) { UINode* UINode::setSkin( const UISkin& Skin ) { removeSkin(); - mSkinState = UISkinState::New( Skin.clone() ); + mSkinState = UISkinState::New( Skin.cloneSkin() ); onThemeLoaded(); @@ -1273,7 +1273,7 @@ UINode* UINode::setSkin( UISkin* skin ) { removeSkin(); - mSkinState = UISkinState::New( skin->clone() ); + mSkinState = UISkinState::New( skin->cloneSkin() ); mSkinState->setState( InitialState ); onThemeLoaded(); diff --git a/src/eepp/ui/uinodedrawable.cpp b/src/eepp/ui/uinodedrawable.cpp index e1271682e..b6cecd7bc 100644 --- a/src/eepp/ui/uinodedrawable.cpp +++ b/src/eepp/ui/uinodedrawable.cpp @@ -415,8 +415,7 @@ UINodeDrawable::LayerDrawable::LayerDrawable( UINodeDrawable* container ) : mAttachmentEq( "scroll" ), mOrigin( Origin::PaddingBox ), mClip( Clip::BorderBox ), - mAttachment( Attachment::Scroll ), - mAsyncDrawableAlive( std::make_shared>( true ) ) {} + mAttachment( Attachment::Scroll ) {} UINodeDrawable::LayerDrawable::~LayerDrawable() { if ( mAsyncDrawableAlive ) @@ -695,6 +694,8 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value Uint64 resourceGeneration = resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; Uint64 loadId = ++mRemoteDrawableLoadId; + if ( !mAsyncDrawableAlive ) + mAsyncDrawableAlive = std::make_shared>( true ); auto alive = mAsyncDrawableAlive; TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); diff --git a/src/eepp/ui/uiskin.cpp b/src/eepp/ui/uiskin.cpp index 9d144db82..5a611a3bd 100644 --- a/src/eepp/ui/uiskin.cpp +++ b/src/eepp/ui/uiskin.cpp @@ -38,7 +38,7 @@ ResourcePtr UISkin::clone( const std::string& newName ) const { for ( const auto& state : mDrawables ) { if ( !state.second ) continue; - DrawablePtr drawable = state.second->createInstance(); + DrawablePtr drawable = state.second->clone(); if ( !drawable ) return {}; skin->setStateDrawable( state.first, std::move( drawable ) ); @@ -48,12 +48,12 @@ ResourcePtr UISkin::clone( const std::string& newName ) const { return skin; } -ResourcePtr UISkin::clone() const { +ResourcePtr UISkin::cloneSkin() const { return clone( mName ); } -DrawablePtr UISkin::createInstance() const { - return clone(); +DrawablePtr UISkin::clone() const { + return cloneSkin(); } Rectf UISkin::getBorderSize() { diff --git a/src/eepp/ui/uitheme.cpp b/src/eepp/ui/uitheme.cpp index a20bb6e9d..f3c0524cc 100644 --- a/src/eepp/ui/uitheme.cpp +++ b/src/eepp/ui/uitheme.cpp @@ -101,7 +101,7 @@ UITheme* UITheme::loadFromTextureAtlas( UITheme* tTheme, Graphics::TextureAtlas* if ( String::startsWith( name, sAbbrIcon ) ) { auto* icon = UIIcon::New( name.substr( sAbbrIcon.size() ) ); icon->setSource( textureRegion->getPixelsSize().getWidth(), - textureRegion->createInstance() ); + textureRegion->clone() ); tTheme->getIconTheme()->add( icon ); } else if ( String::startsWith( name, sAbbr ) ) { std::vector dotParts = String::split( name, '.' ); @@ -141,7 +141,7 @@ UITheme* UITheme::loadFromTextureAtlas( UITheme* tTheme, Graphics::TextureAtlas* int stateNum = UIState::getStateNumber( nameParts[nameParts.size() - 1] ); if ( -1 != stateNum ) - skins[skinName]->setStateDrawable( stateNum, drawable->createInstance() ); + skins[skinName]->setStateDrawable( stateNum, drawable->clone() ); } else { std::vector nameParts = String::split( name, '_' ); @@ -157,7 +157,7 @@ UITheme* UITheme::loadFromTextureAtlas( UITheme* tTheme, Graphics::TextureAtlas* if ( -1 != stateNum ) skins[skinName]->setStateDrawable( stateNum, - textureRegion->createInstance() ); + textureRegion->clone() ); } } } @@ -201,7 +201,7 @@ UITheme* UITheme::loadFromDirectory( UITheme* tTheme, const std::string& Path, TextureRegion::New( TextureFactory::instance()->loadFromFile( fpath ), name ); tSG->add( drawable ); auto* icon = UIIcon::New( name.substr( sAbbrIcon.size() ) ); - icon->setSource( drawable->getPixelsSize().getWidth(), drawable->createInstance() ); + icon->setSource( drawable->getPixelsSize().getWidth(), drawable->clone() ); tTheme->getIconTheme()->add( icon ); } else if ( String::startsWith( name, sAbbr ) ) { std::vector dotParts = String::split( name, '.' ); @@ -242,7 +242,7 @@ UITheme* UITheme::loadFromDirectory( UITheme* tTheme, const std::string& Path, int stateNum = UIState::getStateNumber( nameParts[nameParts.size() - 1] ); if ( -1 != stateNum ) - skins[skinName]->setStateDrawable( stateNum, drawable->createInstance() ); + skins[skinName]->setStateDrawable( stateNum, drawable->clone() ); } else { std::vector nameParts = String::split( name, '_' ); @@ -261,7 +261,7 @@ UITheme* UITheme::loadFromDirectory( UITheme* tTheme, const std::string& Path, if ( -1 != stateNum ) skins[skinName]->setStateDrawable( - stateNum, textureRegion->createInstance() ); + stateNum, textureRegion->clone() ); } } } diff --git a/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp b/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp index 23eeca3de..e419073b9 100644 --- a/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp +++ b/src/modules/maps/src/eepp/maps/gameobjecttextureregion.cpp @@ -107,7 +107,7 @@ Graphics::TextureRegion* GameObjectTextureRegion::getTextureRegion() const { void GameObjectTextureRegion::setTextureRegion( Graphics::TextureRegion* TextureRegion ) { mTextureRegion = TextureRegion - ? std::static_pointer_cast( TextureRegion->createInstance() ) + ? std::static_pointer_cast( TextureRegion->clone() ) : TextureRegionPtr{}; } diff --git a/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp b/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp index 4d0a9b5aa..3b42a6a55 100644 --- a/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp +++ b/src/modules/maps/src/eepp/maps/gameobjectvirtual.cpp @@ -18,7 +18,7 @@ GameObjectVirtual::GameObjectVirtual( TextureRegion* TextureRegion, MapLayer* La if ( NULL != TextureRegion ) { mDataId = TextureRegion->getId(); mTextureRegion = std::static_pointer_cast( - TextureRegion->createInstance() ); + TextureRegion->clone() ); } } diff --git a/src/tests/test_all/test.cpp b/src/tests/test_all/test.cpp index 2e33477b7..56e55f81f 100644 --- a/src/tests/test_all/test.cpp +++ b/src/tests/test_all/test.cpp @@ -741,7 +741,7 @@ void EETest::createNewUI() { UISprite* sprite = UISprite::New(); sprite->setFlags( UI_AUTO_SIZE ); sprite->setPosition( 50, 600 )->setParent( container ); - sprite->setSprite( SP.clone() ); + sprite->setSprite( SP.cloneSprite() ); UIScrollBar* scrollBar = UIScrollBar::New(); scrollBar->setOrientation( UIOrientation::Horizontal ) diff --git a/src/tests/unit_tests/resource_prerequisite_tests.cpp b/src/tests/unit_tests/resource_prerequisite_tests.cpp index 63c2e04e8..967018e89 100644 --- a/src/tests/unit_tests/resource_prerequisite_tests.cpp +++ b/src/tests/unit_tests/resource_prerequisite_tests.cpp @@ -108,6 +108,7 @@ class TestDrawableResource : public DrawableResource { bool isStateful() { return false; } void notifyChange() { onResourceChange(); } + bool hasCallbackState() const { return mCallbackState != nullptr; } }; class CountingDrawable : public Drawable { @@ -122,7 +123,7 @@ class CountingDrawable : public Drawable { void draw( const Vector2f&, const Sizef& ) {} bool isStateful() { return false; } - DrawablePtr createInstance() const { + DrawablePtr clone() const { ++*mInstanceCount; auto instance = makeResource( mInstanceCount ); instance->setColor( mColor ); @@ -527,10 +528,12 @@ UTEST( ResourcePrerequisites, textureRegionRetainsItsTexture ) { UTEST( ResourcePrerequisites, drawableResourceConnectionsDisconnectWithTheirLifetime ) { auto resource = makeResource(); + EXPECT_FALSE( resource->hasCallbackState() ); int notifications = 0; { DrawableResourceConnection connection = resource->connectResourceChange( [¬ifications]( DrawableResource& ) { ++notifications; } ); + EXPECT_TRUE( resource->hasCallbackState() ); EXPECT_TRUE( static_cast( connection ) ); resource->notifyChange(); EXPECT_EQ( notifications, 1 ); @@ -546,13 +549,27 @@ UTEST( ResourcePrerequisites, drawableResourceConnectionsDisconnectWithTheirLife expiredConnection.disconnect(); } +UTEST( ResourcePrerequisites, drawableResourceCallbacksCanDisconnectDuringNotification ) { + auto resource = makeResource(); + int notifications = 0; + DrawableResourceConnection connection; + connection = resource->connectResourceChange( [&]( DrawableResource& ) { + ++notifications; + connection.disconnect(); + } ); + + resource->notifyChange(); + resource->notifyChange(); + EXPECT_EQ( notifications, 1 ); +} + UTEST( ResourcePrerequisites, textureCreatesIndependentDrawableInstances ) { EE::Window::Window* window = createLifecycleTestWindow( "Texture drawable instance test" ); TexturePtr texture = TextureFactory::instance()->createEmptyTexture( 2, 2 ); ASSERT_TRUE( texture != nullptr ); - DrawablePtr firstDrawable = texture->createInstance(); - DrawablePtr secondDrawable = texture->createInstance(); + DrawablePtr firstDrawable = texture->clone(); + DrawablePtr secondDrawable = texture->clone(); ASSERT_TRUE( firstDrawable != nullptr ); ASSERT_TRUE( secondDrawable != nullptr ); ASSERT_EQ( firstDrawable->getDrawableType(), Drawable::TEXTUREDRAWABLE ); @@ -620,7 +637,7 @@ UTEST( ResourcePrerequisites, stateListsCloneStateAndChildrenIndependently ) { source->setStateDrawable( 2, secondRegion ); source->setState( 1 ); - DrawablePtr drawableInstance = source->createInstance(); + DrawablePtr drawableInstance = source->clone(); ASSERT_TRUE( drawableInstance != nullptr ); ASSERT_EQ( drawableInstance->getDrawableType(), Drawable::STATELIST ); auto instance = std::static_pointer_cast( drawableInstance ); @@ -659,7 +676,7 @@ UTEST( ResourcePrerequisites, spritesCloneFramesAndAnimationStateIndependently ) source->addFrame( texture, Sizef( 2.f, 2.f ) ); source->setCurrentFrame( 0 ); - SpritePtr instance = source->clone(); + SpritePtr instance = source->cloneSprite(); ASSERT_TRUE( instance != nullptr ); TextureRegion* sourceFrame = source->getTextureRegion( 0 ); TextureRegion* instanceFrame = instance->getTextureRegion( 0 ); @@ -724,7 +741,7 @@ UTEST( ResourcePrerequisites, drawableGroupsAndVariantsHoldSafeIndependentHandle auto rectangle = makeResource( Vector2f( 1.f, 2.f ), Sizef( 3.f, 4.f ) ); source->addDrawable( rectangle ); - DrawablePtr drawableInstance = source->createInstance(); + DrawablePtr drawableInstance = source->clone(); ASSERT_TRUE( drawableInstance != nullptr ); ASSERT_EQ( drawableInstance->getDrawableType(), Drawable::GROUP ); auto instance = std::static_pointer_cast( drawableInstance ); @@ -898,7 +915,7 @@ UTEST( ResourcePrerequisites, engineTeardownReleasesGraphicsBeforeContextsAcross auto* scene = UISceneNode::New(); SceneManager::instance()->add( scene ); scene->enableFrameBuffer(); - UIImage::New()->setDrawable( ninePatch->createInstance() )->setParent( scene->getRoot() ); + UIImage::New()->setDrawable( ninePatch->clone() )->setParent( scene->getRoot() ); auto* font = FontTrueType::New( "engine-teardown-font" ); ASSERT_TRUE( diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index c810c3a5c..93076a0e4 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -1451,21 +1451,6 @@ std::string AutoCompletePlugin::getPartialSymbol( TextDocument* doc ) { } void AutoCompletePlugin::update( UICodeEditor* editor ) { - const int iconSize = PixelDensity::dpToPxI( 12 ); - if ( mSuggestionIconDrawableSize != iconSize ) { - mSuggestionIconDrawables.clear(); - mSuggestionIconDrawableSize = iconSize; - } - for ( const auto& suggestion : mSuggestions ) { - const int iconKind = (int)suggestion.kind; - if ( mSuggestionIconDrawables.find( iconKind ) != mSuggestionIconDrawables.end() ) - continue; - UIIcon* icon = editor->getUISceneNode()->findIcon( - LSPCompletionItemHelper::toIconString( suggestion.kind ) ); - if ( icon ) - mSuggestionIconDrawables[iconKind] = icon->createDrawable( iconSize ); - } - for ( auto clientIt = mSnippetClients.begin(); clientIt != mSnippetClients.end(); ) { if ( !clientIt->second->isAttached() ) clientIt = mSnippetClients.erase( clientIt ); @@ -1717,8 +1702,11 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc text.draw( cursorPos.x + iconSpace.getWidth() + mBoxPadding.Left, cursorPos.y + mRowHeight * count + mBoxPadding.Top ); - auto iconIt = mSuggestionIconDrawables.find( (int)suggestion.kind ); - DrawablePtr icon = iconIt != mSuggestionIconDrawables.end() ? iconIt->second : DrawablePtr{}; + Drawable* icon = nullptr; + UIIcon* iconSource = editor->getUISceneNode()->findIcon( + LSPCompletionItemHelper::toIconString( suggestion.kind ) ); + if ( iconSource ) + icon = iconSource->getSource( PixelDensity::dpToPxI( 12 ) ).get(); if ( icon ) { Color iconColor( icon->getColor() ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index ed4e45f11..0ed35f94d 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -187,8 +187,6 @@ class AutoCompletePlugin : public Plugin { Float mRowHeight{ 0 }; Rectf mBoxRect; - UnorderedMap mSuggestionIconDrawables; - int mSuggestionIconDrawableSize{ 0 }; struct SnippetTabStopOccurrence { TextRange range; diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 15edcf137..cc96c43ae 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -183,23 +183,6 @@ DebuggerPlugin::~DebuggerPlugin() { } } -void DebuggerPlugin::update( UICodeEditor* editor ) { - if ( mBreakpointIcon == nullptr ) - mBreakpointIcon = getUISceneNode()->findIcon( "circle-perfect" ); - if ( mBreakpointIcon ) { - const int iconSize = (int)eefloor( editor->getLineHeight() * 0.875f ); - if ( mBreakpointDrawables.find( iconSize ) == mBreakpointDrawables.end() ) - mBreakpointDrawables[iconSize] = mBreakpointIcon->createDrawable( iconSize ); - } - if ( mStackFrameIcon == nullptr ) - mStackFrameIcon = getUISceneNode()->findIcon( "debug-stackframe" ); - if ( mStackFrameIcon ) { - const int iconSize = (int)eefloor( editor->getLineHeight() ); - if ( mStackFrameDrawables.find( iconSize ) == mStackFrameDrawables.end() ) - mStackFrameDrawables[iconSize] = mStackFrameIcon->createDrawable( iconSize ); - } -} - void DebuggerPlugin::onSaveProject( const std::string& /*projectFolder*/, const std::string& projectStatePath, bool rewriteStateOnlyIfNeeded ) { @@ -1619,11 +1602,12 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, .blendAlpha( editor->getAlpha() ) ); bool iconDrawn = false; + if ( mBreakpointIcon == nullptr ) + mBreakpointIcon = getUISceneNode()->findIcon( "circle-perfect" ); if ( mBreakpointIcon ) { Float finalHeight = eefloor( radius * 1.75f ); - auto drawableIt = mBreakpointDrawables.find( (int)finalHeight ); - if ( drawableIt != mBreakpointDrawables.end() && drawableIt->second ) { - DrawablePtr& drawable = drawableIt->second; + Drawable* drawable = mBreakpointIcon->getSource( (int)finalHeight ).get(); + if ( drawable ) { Color oldColor = drawable->getColor(); drawable->setColor( color ); drawable->draw( @@ -1664,12 +1648,16 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, Float dim = radius * 2; Float gutterSpace = editor->getGutterSpace( this ); + if ( mStackFrameIcon == nullptr ) + mStackFrameIcon = getUISceneNode()->findIcon( "debug-stackframe" ); if ( mStackFrameIcon ) { const int iconSize = (int)eefloor( lineHeight ); - auto drawableIt = mStackFrameDrawables.find( iconSize ); - if ( drawableIt != mStackFrameDrawables.end() && drawableIt->second ) { - drawableIt->second->setColor( color ); - drawableIt->second->draw( lnPos.floor() ); + Drawable* drawable = mStackFrameIcon->getSource( iconSize ).get(); + if ( drawable ) { + Color oldColor = drawable->getColor(); + drawable->setColor( color ); + drawable->draw( lnPos.floor() ); + drawable->setColor( oldColor ); return; } } diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index 605920f8d..6690f6c60 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -139,9 +139,7 @@ class DebuggerPlugin : public PluginBase { std::string mCurConfiguration; std::vector mRegisteredCommands; UIIcon* mBreakpointIcon{ nullptr }; - UnorderedMap mBreakpointDrawables; UIIcon* mStackFrameIcon{ nullptr }; - UnorderedMap mStackFrameDrawables; class DebuggerPluginClient : public TextDocument::Client { public: @@ -218,8 +216,6 @@ class DebuggerPlugin : public PluginBase { void onRegisterDocument( TextDocument* doc ) override; - void update( UICodeEditor* editor ) override; - void drawLineNumbersBefore( UICodeEditor* editor, const DocumentLineRange& lineRange, const Vector2f& startScroll, const Vector2f& screenStart, const Float& lineHeight, const Float& lineNumberWidth, diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index fa627e1e7..89e7305a7 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -744,15 +744,6 @@ void LinterPlugin::onUnregister( UICodeEditor* editor ) { } void LinterPlugin::update( UICodeEditor* editor ) { - if ( mLightbulbIcon == nullptr ) - mLightbulbIcon = editor->getUISceneNode()->getUIIconThemeManager()->findIcon( - "lightbulb-autofix" ); - if ( mLightbulbIcon ) { - const int iconSize = (int)eefloor( editor->getLineHeight() ); - if ( mLightbulbDrawables.find( iconSize ) == mLightbulbDrawables.end() ) - mLightbulbDrawables[iconSize] = mLightbulbIcon->createDrawable( iconSize ); - } - std::shared_ptr doc = editor->getDocumentRef(); auto it = mDirtyDoc.find( doc.get() ); if ( it != mDirtyDoc.end() && it->second->getElapsedTime() >= mDelayTime ) { @@ -1153,12 +1144,15 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index, if ( !match.diagnostic.codeActions.empty() ) { Color wcolor( editor->getColorScheme().getEditorSyntaxStyle( "warning"_sst ).color ); + if ( mLightbulbIcon == nullptr ) + mLightbulbIcon = + editor->getUISceneNode()->getUIIconThemeManager()->findIcon( + "lightbulb-autofix" ); if ( nullptr != mLightbulbIcon ) { const int iconSize = (int)eefloor( lineHeight ); - auto drawableIt = mLightbulbDrawables.find( iconSize ); - if ( drawableIt == mLightbulbDrawables.end() || drawableIt->second == nullptr ) + Drawable* drawable = mLightbulbIcon->getSource( iconSize ).get(); + if ( drawable == nullptr ) return; - DrawablePtr& drawable = drawableIt->second; Color oldColor( drawable->getColor() ); drawable->setColor( wcolor ); diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index 8275b0b53..7dd8df2f0 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -157,7 +157,6 @@ class LinterPlugin : public Plugin { std::set mLSPLanguagesDisabled; String::HashType mConfigHash{ 0 }; UIIcon* mLightbulbIcon{ nullptr }; - UnorderedMap mLightbulbDrawables; std::string mErrorMsg; Rectf mQuickFixRect; std::string mOldMaxWidth; diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 130abf6d4..19ac79f69 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -271,42 +271,8 @@ LSPClientPlugin::~LSPClientPlugin() { } } -void LSPClientPlugin::update( UICodeEditor* editor ) { +void LSPClientPlugin::update( UICodeEditor* ) { mClientManager.updateDirty(); - - if ( !mBreadcrumb ) - return; - Font* font = getUISceneNode()->getUIThemeManager()->getDefaultFont(); - if ( !font ) - return; - - const Float fontSize = getUISceneNode()->getUIThemeManager()->getDefaultFontSize(); - const int separatorSize = - PixelDensity::dpToPxI( font->getLineSpacing( fontSize ) * 0.5f ); - if ( mDrawSepIcon == nullptr ) - mDrawSepIcon = getUISceneNode()->findIcon( "chevron-right" ); - if ( mDrawSepIcon && - ( mDrawSepDrawable == nullptr || mDrawSepDrawableSize != separatorSize ) ) { - mDrawSepDrawable = mDrawSepIcon->createDrawable( separatorSize ); - mDrawSepDrawableSize = separatorSize; - } - - const int symbolIconSize = (int)fontSize; - if ( mBreadcrumbIconDrawableSize != symbolIconSize ) { - mBreadcrumbIconDrawables.clear(); - mBreadcrumbIconDrawableSize = symbolIconSize; - } - Lock l( mDocCurrentSymbolsMutex ); - auto symbolsIt = mDocCurrentSymbols.find( editor->getDocument().getURI() ); - if ( symbolsIt == mDocCurrentSymbols.end() ) - return; - for ( const auto& symbol : symbolsIt->second ) { - if ( mBreadcrumbIconDrawables.find( symbol.icon ) != mBreadcrumbIconDrawables.end() ) - continue; - UIIcon* icon = getUISceneNode()->findIcon( symbol.icon ); - if ( icon ) - mBreadcrumbIconDrawables[symbol.icon] = icon->createDrawable( symbolIconSize ); - } } struct LSPPositionAndServer { @@ -1998,28 +1964,34 @@ void LSPClientPlugin::drawTop( UICodeEditor* editor, const Vector2f& screenStart pos.x += drawn.getWidth(); Float textHeight = drawn.getHeight(); + if ( mDrawSepIcon == nullptr ) + mDrawSepIcon = getUISceneNode()->findIcon( "chevron-right" ); + Drawable* separatorDrawable = + mDrawSepIcon + ? mDrawSepIcon->getSource( PixelDensity::dpToPxI( drawn.getHeight() * 0.5f ) ).get() + : nullptr; const auto& symbolsInfo = symbolsInfoIt->second; for ( const auto& info : symbolsInfo ) { - if ( mDrawSepDrawable ) { + if ( separatorDrawable ) { pos.x += eefloor( PixelDensity::dpToPx( 8 ) ); - Color c = mDrawSepDrawable->getColor(); - mDrawSepDrawable->setColor( textColor ); - Float iconHeight = mDrawSepDrawable->getPixelsSize().getHeight(); + Color c = separatorDrawable->getColor(); + separatorDrawable->setColor( textColor ); + Float iconHeight = separatorDrawable->getPixelsSize().getHeight(); Vector2f iconPos( { pos.x, screenStart.y + textOffsetY + eefloor( ( textHeight - iconHeight ) * 0.5f ) } ); - mDrawSepDrawable->draw( iconPos ); + separatorDrawable->draw( iconPos ); pos.x += - mDrawSepDrawable->getPixelsSize().getWidth() + eefloor( PixelDensity::dpToPx( 8 ) ); - mDrawSepDrawable->setColor( c ); + separatorDrawable->getPixelsSize().getWidth() + eefloor( PixelDensity::dpToPx( 8 ) ); + separatorDrawable->setColor( c ); } else { pos.x += eefloor( PixelDensity::dpToPx( 16 ) ); } - auto iconIt = mBreadcrumbIconDrawables.find( info.icon ); - if ( iconIt != mBreadcrumbIconDrawables.end() && iconIt->second ) { - DrawablePtr& iconDrawable = iconIt->second; + UIIcon* icon = getUISceneNode()->findIcon( info.icon ); + Drawable* iconDrawable = icon ? icon->getSource( (int)fontSize ).get() : nullptr; + if ( iconDrawable ) { Color c = iconDrawable->getColor(); iconDrawable->setColor( textColor ); Float iconHeight = iconDrawable->getPixelsSize().getHeight(); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index bb50c7780..4d8fac0c4 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -149,10 +149,6 @@ class LSPClientPlugin : public Plugin { }; UnorderedMap> mDocCurrentSymbols; UIIcon* mDrawSepIcon{ nullptr }; - DrawablePtr mDrawSepDrawable; - int mDrawSepDrawableSize{ 0 }; - UnorderedMap mBreadcrumbIconDrawables; - int mBreadcrumbIconDrawableSize{ 0 }; std::string mConfigFileError; LSPClientPlugin( PluginManager* pluginManager, bool sync ); From f7dba042df5242368078801019ab2cac2c4ead9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Tue, 21 Jul 2026 23:50:58 -0300 Subject: [PATCH 06/18] Replace DrawableSearcher with scoped drawable resolution: Add a scene-owned UI::DrawableResolver for resolving file, data, HTTP, and named drawable references through each UISceneNode's current resource scope. Move generic texture, atlas region, nine-patch, and sprite lookup into Graphics::ResourceScope, preserving drawable resolution for pure Graphics consumers without introducing a UI dependency. Migrate UI images, sprites, menus, CSS parsing, and tests to the new resolver. Resolve CSS icons through the requesting node's scene instead of the global scene, preserving embedded-scene isolation. Remove the obsolete DrawableSearcher API and update platform project manifests, tests, and the shared-resource architecture plan. --- .../resource_shared_ownership_architecture.md | 14 +- include/eepp/graphics.hpp | 1 - include/eepp/graphics/drawablesearcher.hpp | 30 --- include/eepp/graphics/resourcescope.hpp | 3 + include/eepp/ui.hpp | 1 + include/eepp/ui/drawableresolver.hpp | 31 +++ include/eepp/ui/uiscenenode.hpp | 8 + projects/linux/ee.files | 4 +- projects/macos/ee.files | 4 +- projects/windows/ee.files | 4 +- src/eepp/graphics/drawablesearcher.cpp | 244 ------------------ src/eepp/graphics/resourcescope.cpp | 68 +++++ src/eepp/ui/css/drawableimageparser.cpp | 25 +- src/eepp/ui/drawableresolver.cpp | 160 ++++++++++++ src/eepp/ui/uiimage.cpp | 10 +- src/eepp/ui/uimenu.cpp | 24 +- src/eepp/ui/uipushbutton.cpp | 1 - src/eepp/ui/uiscenenode.cpp | 11 +- src/eepp/ui/uisprite.cpp | 14 +- .../resource_prerequisite_tests.cpp | 18 ++ src/tests/unit_tests/uihtml_tests.cpp | 4 +- 21 files changed, 353 insertions(+), 326 deletions(-) delete mode 100644 include/eepp/graphics/drawablesearcher.hpp create mode 100644 include/eepp/ui/drawableresolver.hpp delete mode 100644 src/eepp/graphics/drawablesearcher.cpp create mode 100644 src/eepp/ui/drawableresolver.cpp diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index 397009c24..a9f8d69c7 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -1,6 +1,6 @@ # eepp shared-resource ownership architecture -Status: active implementation baseline; Stage 0 through Stage 4 complete, 2026-07-20. +Status: active implementation baseline; Stage 0 through Stage 5 complete, 2026-07-21. This document freezes the contracts that must be true before the public texture API is changed. The implementation may refine names and small mechanics, but changing an invariant below requires an @@ -727,6 +727,18 @@ Exit criteria: ### Stage 5: layered UI resolution +Status: complete, 2026-07-21. `DrawableSearcher` was removed from Graphics. `ResourceScope` now +provides scoped drawable lookup for textures, atlas regions, nine-patches, and sprites, preserving a +pure Graphics entry point with no UI dependency. Each `UISceneNode` owns an allocation-free +`UI::DrawableResolver` that reads the scene's current scope and referer when resolving file, data, +HTTP, and named drawable references. UI images, sprites, menus, CSS parsing, and tests use the scene +resolver; callers without a scene explicitly construct one over `defaultResourceScope()`. + +CSS icon resolution now uses the requesting node's scene instead of the process-global scene. +Texture names remain isolated by local/imported catalogs and become visible across scenes only when +their scopes or catalogs are shared intentionally. Atlas, nine-patch, and sprite manager migration +to scoped catalogs remains Stage 7 work for those resource families. + Implement UI::DrawableResolver and replace DrawableSearcher. Scene resolvers delegate Graphics work to their explicit scope. Keep CSS/icon/glyph interpretation in UI and cookie/navigation concerns in Web services. diff --git a/include/eepp/graphics.hpp b/include/eepp/graphics.hpp index c3a8a5dc3..624b55d3a 100644 --- a/include/eepp/graphics.hpp +++ b/include/eepp/graphics.hpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/include/eepp/graphics/drawablesearcher.hpp b/include/eepp/graphics/drawablesearcher.hpp deleted file mode 100644 index c95ea9cb4..000000000 --- a/include/eepp/graphics/drawablesearcher.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef EE_GRAPHICS_DRAWABLEMANAGER_HPP -#define EE_GRAPHICS_DRAWABLEMANAGER_HPP - -#include -#include -#include - -namespace EE { namespace Graphics { - -class ResourceScope; - -class EE_API DrawableSearcher { - public: - static DrawablePtr searchByName( const std::string& name, bool firstSearchSprite = false, - Network::URI referer = "", - ResourceScope* resourceScope = nullptr ); - - static DrawablePtr searchById( const Uint32& id ); - - static void setPrintWarnings( const bool& print ); - - static bool getPrintWarnings(); - - protected: - static bool sPrintWarnings; -}; - -}} // namespace EE::Graphics - -#endif diff --git a/include/eepp/graphics/resourcescope.hpp b/include/eepp/graphics/resourcescope.hpp index 5d6719ae6..60752c466 100644 --- a/include/eepp/graphics/resourcescope.hpp +++ b/include/eepp/graphics/resourcescope.hpp @@ -1,6 +1,7 @@ #ifndef EE_GRAPHICS_RESOURCESCOPE_HPP #define EE_GRAPHICS_RESOURCESCOPE_HPP +#include #include namespace EE { namespace Graphics { @@ -17,6 +18,8 @@ class EE_API ResourceScope { TexturePtr findTexture( const ResourceKey& key ) const; TexturePtr findTexture( const std::string& key ) const; + DrawablePtr findDrawable( const std::string& name, bool firstSearchSprite = false ) const; + DrawablePtr findDrawable( const Uint32& id ) const; void publishLocal( ResourceKey key, TexturePtr texture ); void publishLocal( std::string key, TexturePtr texture ); diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index ee836c590..0765126c1 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eepp/ui/drawableresolver.hpp b/include/eepp/ui/drawableresolver.hpp new file mode 100644 index 000000000..463348e43 --- /dev/null +++ b/include/eepp/ui/drawableresolver.hpp @@ -0,0 +1,31 @@ +#ifndef EE_UI_DRAWABLERESOLVER_HPP +#define EE_UI_DRAWABLERESOLVER_HPP + +#include +#include + +namespace EE { namespace UI { + +class UISceneNode; + +/** Resolves named drawables through an explicit UI scene or Graphics resource scope. */ +class EE_API DrawableResolver { + public: + explicit DrawableResolver( UISceneNode& sceneNode ); + explicit DrawableResolver( Graphics::ResourceScope& resourceScope ); + + Graphics::DrawablePtr resolve( const std::string& name, bool firstSearchSprite = false ) const; + Graphics::DrawablePtr resolveById( const Uint32& id ) const; + + void setPrintWarnings( bool printWarnings ); + bool getPrintWarnings() const; + + protected: + UISceneNode* mSceneNode{ nullptr }; + Graphics::ResourceScope* mResourceScope{ nullptr }; + bool mPrintWarnings{ false }; +}; + +}} // namespace EE::UI + +#endif diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index de70bd2b0..8aae83e45 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -566,6 +567,12 @@ class EE_API UISceneNode : public SceneNode { */ DrawablePtr findIconDrawable( const std::string& iconName, const size_t& drawableSize ); + /** @return This scene's drawable resolver. */ + DrawableResolver& getDrawableResolver(); + + /** @return This scene's drawable resolver. */ + const DrawableResolver& getDrawableResolver() const; + /** * @brief Gets the keybindings manager. * @@ -910,6 +917,7 @@ class EE_API UISceneNode : public SceneNode { UnorderedMap mFontFaceFamilies; std::shared_ptr mAsyncResourceLoadState; Graphics::ResourceScopePtr mResourceScope; + DrawableResolver mDrawableResolver; KeyBindings mKeyBindings; std::map mKeyBindingCommands; UnorderedSet mDirtyStyle; diff --git a/projects/linux/ee.files b/projects/linux/ee.files index b703fcf55..46d4890f7 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -57,7 +57,7 @@ ../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp -../../include/eepp/graphics/drawablesearcher.hpp +../../include/eepp/ui/drawableresolver.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontfamily.hpp @@ -555,7 +555,7 @@ ../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp -../../src/eepp/graphics/drawablesearcher.cpp +../../src/eepp/ui/drawableresolver.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontfamily.cpp diff --git a/projects/macos/ee.files b/projects/macos/ee.files index 466e77c6b..a320ffa16 100644 --- a/projects/macos/ee.files +++ b/projects/macos/ee.files @@ -57,7 +57,7 @@ ../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp -../../include/eepp/graphics/drawablesearcher.hpp +../../include/eepp/ui/drawableresolver.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontfamily.hpp @@ -546,7 +546,7 @@ ../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp -../../src/eepp/graphics/drawablesearcher.cpp +../../src/eepp/ui/drawableresolver.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontfamily.cpp diff --git a/projects/windows/ee.files b/projects/windows/ee.files index 18a331b57..1991069ec 100644 --- a/projects/windows/ee.files +++ b/projects/windows/ee.files @@ -56,7 +56,7 @@ ../../include/eepp/graphics/drawablegroup.hpp ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp -../../include/eepp/graphics/drawablesearcher.hpp +../../include/eepp/ui/drawableresolver.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontmanager.hpp @@ -538,7 +538,7 @@ ../../src/eepp/graphics/drawable.cpp ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp -../../src/eepp/graphics/drawablesearcher.cpp +../../src/eepp/ui/drawableresolver.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontmanager.cpp diff --git a/src/eepp/graphics/drawablesearcher.cpp b/src/eepp/graphics/drawablesearcher.cpp deleted file mode 100644 index 6207820c6..000000000 --- a/src/eepp/graphics/drawablesearcher.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -using namespace EE::Window; -using namespace EE::Network; - -namespace EE { namespace Graphics { - -bool DrawableSearcher::sPrintWarnings = false; - -static DrawablePtr getSprite( const std::string& sprite ) { - std::vector tTextureRegionVec = - TextureAtlasManager::instance()->getTextureRegionsByPattern( sprite ); - - if ( tTextureRegionVec.size() ) { - SpritePtr tSprite = Sprite::New(); - tSprite->createAnimation(); - tSprite->addFrames( tTextureRegionVec ); - - return tSprite; - } - - return {}; -} - -static DrawablePtr searchByNameInternal( const std::string& name, ResourceScope& resourceScope ) { - String::HashType id = String::hash( name ); - Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); - - if ( NULL == source ) { - source = NinePatchManager::instance()->getById( id ); - } - - if ( source ) { - return source->clone(); - } - - TexturePtr texture = resourceScope.findTexture( name ); - return texture ? texture->clone() : DrawablePtr{}; -} - -static DrawablePtr parseDataURI( const std::string& name, ResourceScope& scope ) { - auto hash = MD5::fromString( name ).toHexString(); - TexturePtr texture = scope.findTexture( hash ); - std::string::size_type formatAndEncSep; - if ( !texture && - ( formatAndEncSep = name.find_first_of( ',' ) ) != std::string::npos ) { - std::string decodingType = "urldecode"; - std::string mediaType = name.substr( 0, formatAndEncSep ); - std::string format; - auto parts = String::split( mediaType, ';' ); - if ( parts.empty() ) - return nullptr; - auto formatNamePos = parts[0].find_first_of( '/' ); - if ( formatNamePos + 1 < mediaType.size() ) - format = parts[0].substr( formatNamePos + 1 ); - if ( parts.size() > 1 ) { - for ( size_t i = 1; i < parts.size(); ++i ) { - if ( "base64" == parts[i] ) { - decodingType = parts[i]; - break; - } - } - } - - TexturePtr tex; - if ( !format.empty() && - ( Image::isImageExtension( "." + format ) || format == "svg+xml" ) ) { - Image::FormatConfiguration format; - format.svgScale( PixelDensity::getPixelDensity() ); - if ( decodingType == "base64" ) { - int fileStart = formatAndEncSep + 1; - std::string_view fileBase64 = std::string_view{ name }.substr( fileStart ); - std::string buffer; - int len = Base64::decode( fileBase64, buffer ); - if ( len > 0 ) { - tex = TextureFactory::instance()->loadFromMemory( - (const unsigned char*)buffer.c_str(), buffer.size(), false, - Texture::ClampMode::ClampToEdge, false, false, format ); - } - } else if ( decodingType == "urldecode" ) { - int fileStart = formatAndEncSep + 1; - std::string decoded( URI::decode( name.substr( fileStart ) ) ); - if ( !decoded.empty() ) { - tex = TextureFactory::instance()->loadFromMemory( - (const unsigned char*)decoded.c_str(), decoded.size(), false, - Texture::ClampMode::ClampToEdge, false, false, format ); - } - } - } - - if ( tex ) { - tex->setName( hash ); - scope.publishLocal( hash, tex ); - texture = std::move( tex ); - } - } - return texture ? texture->clone() : DrawablePtr{}; -} - -DrawablePtr DrawableSearcher::searchByName( const std::string& name, bool firstSearchSprite, - Network::URI referer, - ResourceScope* requestedResourceScope ) { - DrawablePtr drawable; - - if ( name.size() ) { - ResourceScope& resourceScope = - requestedResourceScope ? *requestedResourceScope : defaultResourceScope(); - bool searchedSprite = false; - - if ( firstSearchSprite ) { - if ( String::startsWith( name, "@sprite/" ) ) { - drawable = getSprite( name.substr( 8 ) ); - } else { - drawable = getSprite( name ); - } - - if ( drawable ) { - return drawable; - } - - searchedSprite = true; - } - - if ( name[0] == '@' ) { - if ( String::startsWith( name, "@textureregion/" ) ) { - if ( Drawable* source = - TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ) ) - drawable = source->clone(); - } else if ( String::startsWith( name, "@image/" ) ) { - TexturePtr texture = resourceScope.findTexture( name.substr( 7 ) ); - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "@texture/" ) ) { - TexturePtr texture = resourceScope.findTexture( name.substr( 9 ) ); - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) { - drawable = getSprite( name.substr( 8 ) ); - } else if ( String::startsWith( name, "@drawable/" ) ) { - drawable = searchByNameInternal( name.substr( 10 ), resourceScope ); - } else if ( String::startsWith( name, "@9p/" ) ) { - if ( Drawable* source = NinePatchManager::instance()->getByName( name.substr( 4 ) ) ) - drawable = source->clone(); - } else { - drawable = searchByNameInternal( name, resourceScope ); - } - } else if ( String::startsWith( name, "file://" ) ) { - std::string filePath( name.substr( 7 ) ); - -#if EE_PLATFORM == EE_PLATFORM_WIN - if ( filePath.size() >= 3 && filePath[0] == '/' && String::isLetter( filePath[1] ) && - filePath[2] == ':' ) { - filePath = filePath.substr( 1 ); - } -#endif - - FileSystem::filePathRemoveProcessPath( filePath ); - - TexturePtr texture = resourceScope.findTexture( filePath ); - - if ( !texture ) { - TexturePtr tex = TextureFactory::instance()->loadFromFile( filePath ); - - if ( tex ) { - resourceScope.publishLocal( filePath, tex ); - texture = std::move( tex ); - } - } - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "http://" ) || - String::startsWith( name, "https://" ) ) { - TexturePtr texture = resourceScope.findTexture( name ); - - if ( NULL == texture && Engine::instance()->isSharedGLContextEnabled() ) { - texture = TextureFactory::instance()->createEmptyTexture( - 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, - false, name ); - if ( texture ) - resourceScope.publishLocal( name, texture ); - - std::map headers; - if ( !referer.empty() ) - headers["referer"] = referer.toString(); - - Http::getAsync( - [texture, name]( const Http&, Http::Request&, Http::Response& response ) { - if ( response.isOK() && !response.getBody().empty() ) { - Image image( (const Uint8*)&response.getBody()[0], - response.getBody().size() ); - - if ( image.getPixels() != NULL ) - texture->replace( &image ); - } else { - Log::debug( "DrawableSearcher::searchByName: could not download image: " - "%s. Error: %d\n%s", - name, response.getStatus(), response.getBody() ); - } - }, - URI( name ), Seconds( 5 ), {}, headers ); - } - - drawable = texture ? texture->clone() : DrawablePtr{}; - } else if ( String::startsWith( name, "data:image/" ) ) { - drawable = parseDataURI( name, resourceScope ); - } else { - drawable = searchByNameInternal( name, resourceScope ); - } - } - - if ( !drawable && sPrintWarnings ) - Log::warning( "DrawableSearcher::searchByName: \"%s\" not found", name.c_str() ); - - return drawable; -} - -DrawablePtr DrawableSearcher::searchById( const Uint32& id ) { - Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); - DrawablePtr drawable = source ? source->clone() : DrawablePtr{}; - - if ( !drawable && sPrintWarnings ) - Log::warning( "DrawableSearcher::searchById: \"%ld\" not found", id ); - - return drawable; -} - -void DrawableSearcher::setPrintWarnings( const bool& print ) { - sPrintWarnings = print; -} - -bool DrawableSearcher::getPrintWarnings() { - return sPrintWarnings; -} - -}} // namespace EE::Graphics diff --git a/src/eepp/graphics/resourcescope.cpp b/src/eepp/graphics/resourcescope.cpp index 4100b4d5a..8f5d64597 100644 --- a/src/eepp/graphics/resourcescope.cpp +++ b/src/eepp/graphics/resourcescope.cpp @@ -1,5 +1,9 @@ #include +#include +#include #include +#include +#include #include #include @@ -30,6 +34,70 @@ TexturePtr ResourceScope::findTexture( const std::string& key ) const { return {}; } +DrawablePtr ResourceScope::findDrawable( const std::string& name, bool firstSearchSprite ) const { + if ( name.empty() ) + return {}; + + auto findSprite = []( const std::string& pattern ) -> DrawablePtr { + std::vector textureRegions = + TextureAtlasManager::instance()->getTextureRegionsByPattern( pattern ); + if ( textureRegions.empty() ) + return {}; + SpritePtr sprite = Sprite::New(); + sprite->createAnimation(); + sprite->addFrames( textureRegions ); + return sprite; + }; + + bool searchedSprite = false; + if ( firstSearchSprite ) { + DrawablePtr sprite = + findSprite( String::startsWith( name, "@sprite/" ) ? name.substr( 8 ) : name ); + if ( sprite ) + return sprite; + searchedSprite = true; + } + + if ( name[0] == '@' ) { + if ( String::startsWith( name, "@textureregion/" ) ) { + Drawable* source = + TextureAtlasManager::instance()->getTextureRegionByName( name.substr( 12 ) ); + return source ? source->clone() : DrawablePtr{}; + } + if ( String::startsWith( name, "@image/" ) ) { + TexturePtr texture = findTexture( name.substr( 7 ) ); + return texture ? texture->clone() : DrawablePtr{}; + } + if ( String::startsWith( name, "@texture/" ) ) { + TexturePtr texture = findTexture( name.substr( 9 ) ); + return texture ? texture->clone() : DrawablePtr{}; + } + if ( String::startsWith( name, "@sprite/" ) && !searchedSprite ) + return findSprite( name.substr( 8 ) ); + if ( String::startsWith( name, "@drawable/" ) ) + return findDrawable( name.substr( 10 ) ); + if ( String::startsWith( name, "@9p/" ) ) { + Drawable* source = NinePatchManager::instance()->getByName( name.substr( 4 ) ); + return source ? source->clone() : DrawablePtr{}; + } + } + + String::HashType id = String::hash( name ); + Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); + if ( source == nullptr ) + source = NinePatchManager::instance()->getById( id ); + if ( source ) + return source->clone(); + + TexturePtr texture = findTexture( name ); + return texture ? texture->clone() : DrawablePtr{}; +} + +DrawablePtr ResourceScope::findDrawable( const Uint32& id ) const { + Drawable* source = TextureAtlasManager::instance()->getTextureRegionById( id ); + return source ? source->clone() : DrawablePtr{}; +} + void ResourceScope::publishLocal( ResourceKey key, TexturePtr texture ) { publishLocal( key.value(), std::move( texture ) ); } diff --git a/src/eepp/ui/css/drawableimageparser.cpp b/src/eepp/ui/css/drawableimageparser.cpp index 28ce99ebb..2d3771db2 100644 --- a/src/eepp/ui/css/drawableimageparser.cpp +++ b/src/eepp/ui/css/drawableimageparser.cpp @@ -1,11 +1,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -16,7 +14,6 @@ #include using namespace EE::Graphics; -using namespace EE::Scene; namespace EE { namespace UI { namespace CSS { @@ -163,9 +160,8 @@ DrawablePtr DrawableImageParser::createDrawable( const std::string& value, const if ( !functionType.isEmpty() ) { if ( exists( functionType.getName() ) ) return mFuncs[functionType.getName()]( functionType, size, node ); - } else if ( DrawablePtr drawable = DrawableSearcher::searchByName( - value, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ) ) { + } else if ( DrawablePtr drawable = + node->getUISceneNode()->getDrawableResolver().resolve( value ) ) { return drawable; } @@ -909,10 +905,8 @@ void DrawableImageParser::registerBaseParsers() { const auto& param = functionType.getParameters().at( 0 ); if ( functionType.getName() == "url" && !param.empty() && param[0] != '@' && !String::startsWith( param, "data:image/" ) ) { - DrawablePtr drawable = DrawableSearcher::searchByName( - node->getUISceneNode()->solveRelativePath( param ).toString(), false, - node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ); + DrawablePtr drawable = node->getUISceneNode()->getDrawableResolver().resolve( + node->getUISceneNode()->solveRelativePath( param ).toString() ); return drawable; } else if ( functionType.getParameters().size() > 1 && String::startsWith( param, "data:image/" ) ) { @@ -921,20 +915,17 @@ void DrawableImageParser::registerBaseParsers() { cparam += ','; cparam += functionType.getParameters().at( i ); } - DrawablePtr drawable = DrawableSearcher::searchByName( - cparam, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ); + DrawablePtr drawable = + node->getUISceneNode()->getDrawableResolver().resolve( cparam ); return drawable; } - DrawablePtr drawable = DrawableSearcher::searchByName( - param, false, node->getUISceneNode()->getReferer(), - node->getUISceneNode()->getResourceScope().get() ); + DrawablePtr drawable = node->getUISceneNode()->getDrawableResolver().resolve( param ); return drawable; }; mFuncs["icon"] = []( const FunctionString& functionType, const Sizef& size, UINode* node ) -> DrawablePtr { - auto* uiScene = SceneManager::instance()->getUISceneNode(); + auto* uiScene = node->getUISceneNode(); const auto& params = functionType.getParameters(); if ( params.size() < 2 ) return nullptr; diff --git a/src/eepp/ui/drawableresolver.cpp b/src/eepp/ui/drawableresolver.cpp new file mode 100644 index 000000000..2b5e8d35a --- /dev/null +++ b/src/eepp/ui/drawableresolver.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace EE::Graphics; +using namespace EE::Network; +using namespace EE::Window; + +namespace EE { namespace UI { + +namespace { + +DrawablePtr parseDataURI( const std::string& name, ResourceScope& resourceScope ) { + std::string hash = MD5::fromString( name ).toHexString(); + TexturePtr texture = resourceScope.findTexture( hash ); + std::string::size_type formatAndEncSep; + if ( !texture && ( formatAndEncSep = name.find_first_of( ',' ) ) != std::string::npos ) { + std::string decodingType = "urldecode"; + std::string mediaType = name.substr( 0, formatAndEncSep ); + std::string formatName; + auto parts = String::split( mediaType, ';' ); + if ( parts.empty() ) + return {}; + auto formatNamePos = parts[0].find_first_of( '/' ); + if ( formatNamePos + 1 < mediaType.size() ) + formatName = parts[0].substr( formatNamePos + 1 ); + for ( size_t i = 1; i < parts.size(); ++i ) { + if ( parts[i] == "base64" ) { + decodingType = parts[i]; + break; + } + } + + TexturePtr decodedTexture; + if ( !formatName.empty() && + ( Image::isImageExtension( "." + formatName ) || formatName == "svg+xml" ) ) { + Image::FormatConfiguration format; + format.svgScale( PixelDensity::getPixelDensity() ); + std::string_view encoded = std::string_view{ name }.substr( formatAndEncSep + 1 ); + if ( decodingType == "base64" ) { + std::string buffer; + if ( Base64::decode( encoded, buffer ) > 0 ) { + decodedTexture = TextureFactory::instance()->loadFromMemory( + reinterpret_cast( buffer.data() ), buffer.size(), + false, Texture::ClampMode::ClampToEdge, false, false, format ); + } + } else { + std::string decoded = URI::decode( encoded ); + if ( !decoded.empty() ) { + decodedTexture = TextureFactory::instance()->loadFromMemory( + reinterpret_cast( decoded.data() ), decoded.size(), + false, Texture::ClampMode::ClampToEdge, false, false, format ); + } + } + } + + if ( decodedTexture ) { + decodedTexture->setName( hash ); + resourceScope.publishLocal( hash, decodedTexture ); + texture = std::move( decodedTexture ); + } + } + + return texture ? texture->clone() : DrawablePtr{}; +} + +} // namespace + +DrawableResolver::DrawableResolver( UISceneNode& sceneNode ) : mSceneNode( &sceneNode ) {} + +DrawableResolver::DrawableResolver( ResourceScope& resourceScope ) : + mResourceScope( &resourceScope ) {} + +DrawablePtr DrawableResolver::resolve( const std::string& name, bool firstSearchSprite ) const { + DrawablePtr drawable; + if ( name.empty() ) + return drawable; + + ResourceScope& resourceScope = mSceneNode ? *mSceneNode->getResourceScope() : *mResourceScope; + if ( String::startsWith( name, "file://" ) ) { + std::string filePath = name.substr( 7 ); +#if EE_PLATFORM == EE_PLATFORM_WIN + if ( filePath.size() >= 3 && filePath[0] == '/' && String::isLetter( filePath[1] ) && + filePath[2] == ':' ) + filePath.erase( 0, 1 ); +#endif + FileSystem::filePathRemoveProcessPath( filePath ); + TexturePtr texture = resourceScope.findTexture( filePath ); + if ( !texture ) { + texture = TextureFactory::instance()->loadFromFile( filePath ); + if ( texture ) + resourceScope.publishLocal( filePath, texture ); + } + drawable = texture ? texture->clone() : DrawablePtr{}; + } else if ( String::startsWith( name, "http://" ) || String::startsWith( name, "https://" ) ) { + TexturePtr texture = resourceScope.findTexture( name ); + if ( !texture && Engine::instance()->isSharedGLContextEnabled() ) { + texture = TextureFactory::instance()->createEmptyTexture( + 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, + name ); + if ( texture ) + resourceScope.publishLocal( name, texture ); + + Http::Request::FieldTable headers; + if ( mSceneNode && !mSceneNode->getReferer().empty() ) + headers["referer"] = mSceneNode->getReferer().toString(); + Http::getAsync( + [texture, name]( const Http&, Http::Request&, Http::Response& response ) { + if ( response.isOK() && !response.getBody().empty() ) { + Image image( reinterpret_cast( response.getBody().data() ), + response.getBody().size() ); + if ( image.getPixels() ) + texture->replace( &image ); + } else { + Log::debug( + "DrawableResolver::resolve: could not download image: %s. Error: " + "%d\n%s", + name, response.getStatus(), response.getBody() ); + } + }, + URI( name ), Seconds( 5 ), {}, headers ); + } + drawable = texture ? texture->clone() : DrawablePtr{}; + } else if ( String::startsWith( name, "data:image/" ) ) { + drawable = parseDataURI( name, resourceScope ); + } else { + drawable = resourceScope.findDrawable( name, firstSearchSprite ); + } + + if ( !drawable && mPrintWarnings ) + Log::warning( "DrawableResolver::resolve: \"%s\" not found", name.c_str() ); + return drawable; +} + +DrawablePtr DrawableResolver::resolveById( const Uint32& id ) const { + ResourceScope& resourceScope = mSceneNode ? *mSceneNode->getResourceScope() : *mResourceScope; + DrawablePtr drawable = resourceScope.findDrawable( id ); + if ( !drawable && mPrintWarnings ) + Log::warning( "DrawableResolver::resolveById: \"%ld\" not found", id ); + return drawable; +} + +void DrawableResolver::setPrintWarnings( bool printWarnings ) { + mPrintWarnings = printWarnings; +} + +bool DrawableResolver::getPrintWarnings() const { + return mPrintWarnings; +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 2b6baef26..36db9eaef 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -555,9 +554,12 @@ bool UIImage::applyProperty( const StyleSheetProperty& attribute ) { if ( createdDrawable ) { setDrawable( std::move( createdDrawable ) ); } else { - setDrawable( DrawableSearcher::searchByName( - path, false, scene ? scene->getReferer() : URI(), - scene ? scene->getResourceScope().get() : nullptr ) ); + if ( scene ) { + setDrawable( scene->getDrawableResolver().resolve( path ) ); + } else { + DrawableResolver resolver( defaultResourceScope() ); + setDrawable( resolver.resolve( path ) ); + } } break; } diff --git a/src/eepp/ui/uimenu.cpp b/src/eepp/ui/uimenu.cpp index 0edc8ea03..68e5c5831 100644 --- a/src/eepp/ui/uimenu.cpp +++ b/src/eepp/ui/uimenu.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -553,18 +552,23 @@ Uint32 UIMenu::onKeyDown( const KeyEvent& event ) { return UIWidget::onKeyDown( event ); } -static DrawablePtr getIconDrawable( const std::string& name, - UIIconThemeManager* iconThemeManager ) { +static DrawablePtr getIconDrawable( const std::string& name, UISceneNode* sceneNode ) { DrawablePtr iconDrawable; - if ( nullptr != iconThemeManager ) { - UIIcon* icon = iconThemeManager->findIcon( name ); + if ( sceneNode ) { + UIIcon* icon = sceneNode->findIcon( name ); if ( icon ) { // TODO: Fix size iconDrawable = icon->createDrawable( PixelDensity::dpToPx( 16 ) ); } } - if ( nullptr == iconDrawable ) - iconDrawable = DrawableSearcher::searchByName( name ); + if ( !iconDrawable ) { + if ( sceneNode ) { + iconDrawable = sceneNode->getDrawableResolver().resolve( name ); + } else { + DrawableResolver resolver( defaultResourceScope() ); + iconDrawable = resolver.resolve( name ); + } + } return iconDrawable; } @@ -579,8 +583,7 @@ void UIMenu::loadFromXmlNode( const pugi::xml_node& node ) { std::string text( item.attribute( "text" ).as_string() ); std::string icon( item.attribute( "icon" ).as_string() ); if ( nullptr != mSceneNode && mSceneNode->isUISceneNode() ) - add( getTranslatorString( text ), - getIconDrawable( icon, getUISceneNode()->getUIIconThemeManager() ) ); + add( getTranslatorString( text ), getIconDrawable( icon, getUISceneNode() ) ); } else if ( name == "menuseparator" || name == "separator" ) { addSeparator(); } else if ( name == "menucheckbox" || name == "checkbox" ) { @@ -598,8 +601,7 @@ void UIMenu::loadFromXmlNode( const pugi::xml_node& node ) { if ( nullptr != getDrawInvalidator() ) subMenu->setParent( getDrawInvalidator() ); subMenu->loadFromXmlNode( item ); - addSubMenu( getTranslatorString( text ), - getIconDrawable( icon, getUISceneNode()->getUIIconThemeManager() ), + addSubMenu( getTranslatorString( text ), getIconDrawable( icon, getUISceneNode() ), subMenu ); } } diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index fd913a2c5..ed85c12a3 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 450d4e939..03b921e93 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -142,6 +142,7 @@ UISceneNode::UISceneNode( EE::Window::Window* window ) : mUIIconThemeManager( UIIconThemeManager::New()->setFallbackThemeManager( mUIThemeManager ) ), mAsyncResourceLoadState( std::make_shared() ), mResourceScope( ResourceScope::New() ), + mDrawableResolver( *this ), mKeyBindings( mWindow->getInput() ) { // Reset size since the SceneNode already set it but needs to set the size from zero to emit // the required events to its children. @@ -1348,11 +1349,19 @@ UIIcon* UISceneNode::findIcon( const std::string& iconName ) { } DrawablePtr UISceneNode::findIconDrawable( const std::string& iconName, - const size_t& drawableSize ) { + const size_t& drawableSize ) { UIIcon* icon = findIcon( iconName ); return icon ? icon->createDrawable( drawableSize ) : DrawablePtr{}; } +DrawableResolver& UISceneNode::getDrawableResolver() { + return mDrawableResolver; +} + +const DrawableResolver& UISceneNode::getDrawableResolver() const { + return mDrawableResolver; +} + CSS::MediaFeatures UISceneNode::getMediaFeatures() const { CSS::MediaFeatures media; const Sizef& viewportSize = getViewportPixelsSize(); diff --git a/src/eepp/ui/uisprite.cpp b/src/eepp/ui/uisprite.cpp index 5eb1eb4d5..3b83c2ad1 100644 --- a/src/eepp/ui/uisprite.cpp +++ b/src/eepp/ui/uisprite.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -216,13 +215,14 @@ bool UISprite::applyProperty( const StyleSheetProperty& attribute ) { path = func.getParameters().at( 0 ); } - DrawablePtr res; UISceneNode* scene = getUISceneNode(); - if ( scene ) - res = DrawableSearcher::searchByName( path, true, scene->getReferer(), - scene->getResourceScope().get() ); - else - res = DrawableSearcher::searchByName( path, true ); + DrawablePtr res; + if ( scene ) { + res = scene->getDrawableResolver().resolve( path, true ); + } else { + DrawableResolver resolver( defaultResourceScope() ); + res = resolver.resolve( path, true ); + } if ( res ) { switch ( res->getDrawableType() ) { diff --git a/src/tests/unit_tests/resource_prerequisite_tests.cpp b/src/tests/unit_tests/resource_prerequisite_tests.cpp index 967018e89..7f1573c21 100644 --- a/src/tests/unit_tests/resource_prerequisite_tests.cpp +++ b/src/tests/unit_tests/resource_prerequisite_tests.cpp @@ -372,6 +372,11 @@ UTEST( ResourcePrerequisites, resourceScopesResolveOnlyLocalAndExplicitlyImporte EXPECT_EQ( first.get(), firstScope->findTexture( "same-name" ).get() ); EXPECT_EQ( second.get(), secondScope->findTexture( "same-name" ).get() ); + DrawablePtr scopedDrawable = firstScope->findDrawable( "same-name" ); + ASSERT_TRUE( scopedDrawable != nullptr ); + ASSERT_EQ( scopedDrawable->getDrawableType(), Drawable::TEXTUREDRAWABLE ); + EXPECT_EQ( first.get(), + static_cast( scopedDrawable.get() )->getTexture().get() ); EXPECT_EQ( shared.get(), firstScope->findTexture( "shared-name" ).get() ); EXPECT_TRUE( secondScope->findTexture( "shared-name" ) == nullptr ); EXPECT_TRUE( firstScope->findTexture( "observed-only" ) == nullptr ); @@ -382,6 +387,7 @@ UTEST( ResourcePrerequisites, resourceScopesResolveOnlyLocalAndExplicitlyImporte TextureWeakPtr externallyRetainedWeak = first; TexturePtr externallyRetained = first; + scopedDrawable.reset(); firstScope.reset(); EXPECT_FALSE( externallyRetainedWeak.expired() ); @@ -423,10 +429,22 @@ UTEST( ResourcePrerequisites, uiScenesOwnIsolatedScopesThatCanBeSharedExplicitly firstScene->getResourceScope()->publishLocal( "scene-texture", texture ); EXPECT_TRUE( secondScene->getResourceScope()->findTexture( "scene-texture" ) == nullptr ); + DrawablePtr firstDrawable = firstScene->getDrawableResolver().resolve( "scene-texture" ); + ASSERT_TRUE( firstDrawable != nullptr ); + ASSERT_EQ( firstDrawable->getDrawableType(), Drawable::TEXTUREDRAWABLE ); + EXPECT_EQ( texture.get(), + static_cast( firstDrawable.get() )->getTexture().get() ); + EXPECT_TRUE( secondScene->getDrawableResolver().resolve( "scene-texture" ) == nullptr ); secondScene->setResourceScope( firstScene->getResourceScope() ); EXPECT_EQ( texture.get(), secondScene->getResourceScope()->findTexture( "scene-texture" ).get() ); + DrawablePtr sharedDrawable = secondScene->getDrawableResolver().resolve( "scene-texture" ); + ASSERT_TRUE( sharedDrawable != nullptr ); + EXPECT_EQ( texture.get(), + static_cast( sharedDrawable.get() )->getTexture().get() ); + sharedDrawable.reset(); + firstDrawable.reset(); texture.reset(); eeDelete( secondScene ); eeDelete( firstScene ); diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 581587497..5dfac9521 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -1,7 +1,6 @@ #include "compareimages.hpp" #include "utest.hpp" -#include #include #include #include @@ -5142,8 +5141,7 @@ UTEST( UIHTML, DeferredFileImageReusesCachedTexture ) { sceneNode->setURI( URI( "file://" + processPath ) ); URI imageURI = sceneNode->solveRelativePath( URI( "../assets/icon/ee.png" ) ); ASSERT_TRUE( FileSystem::fileExists( imageURI.getFSPath() ) ); - DrawablePtr cached = DrawableSearcher::searchByName( - imageURI.toString(), false, sceneNode->getReferer(), sceneNode->getResourceScope().get() ); + DrawablePtr cached = sceneNode->getDrawableResolver().resolve( imageURI.toString() ); ASSERT_TRUE( cached != nullptr ); sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html( From 2d8693295c14faab5461e848763ee0ca58c47c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 22 Jul 2026 02:22:38 -0300 Subject: [PATCH 07/18] add shared WebView resource cache: Introduce WebResourceCache with partitioned sharing, per-document sessions, navigation generations, leases, and canonical request keys. Coalesce concurrent document, stylesheet, font, and image requests while discarding stale navigation subscribers independently. Retain completed resources with TTL/LRU and byte-budget policies, and periodically prune expired unleased entries from UIWebView. Route UISceneNode, UIWebView, UIImage, CSS background images, and remote drawable resolution through the shared cache. Preserve ResourceScope entries as explicit overrides and allow multiple WebViews to share a cache partition. Keep image decode and texture replacement on the guarded graphics-thread completion path. Exclude volatile Cookie and Referer values from cache identity while preserving partition isolation and content-affecting variants. Start TTL retention when the final document lease is released so Back/Forward navigation can reuse resources regardless of how long the previous page was open. Avoid unstable pointers and iterators into unordered_dense storage when tracking leases and pruning entries. Add ASan regression coverage for dense map growth, navigation, eviction, partition isolation, request coalescing, retry behavior, and cache retention. Fully document cache partitions, sessions, generations, request identity, ownership, threading, and maintenance behavior. --- .../resource_shared_ownership_architecture.md | 32 +- include/eepp/ui.hpp | 1 + include/eepp/ui/uinodedrawable.hpp | 3 - include/eepp/ui/uiscenenode.hpp | 18 + include/eepp/ui/uiwebview.hpp | 6 + include/eepp/ui/webresourcecache.hpp | 302 +++++++++++ projects/linux/ee.files | 2 + projects/macos/ee.files | 2 + projects/windows/ee.files | 2 + src/eepp/ui/drawableresolver.cpp | 16 +- src/eepp/ui/uiimage.cpp | 76 +-- src/eepp/ui/uinodedrawable.cpp | 74 +-- src/eepp/ui/uiscenenode.cpp | 214 +++++--- src/eepp/ui/uiwebview.cpp | 73 ++- src/eepp/ui/webresourcecache.cpp | 476 ++++++++++++++++++ .../unit_tests/webresourcecache_tests.cpp | 261 ++++++++++ 16 files changed, 1339 insertions(+), 219 deletions(-) create mode 100644 include/eepp/ui/webresourcecache.hpp create mode 100644 src/eepp/ui/webresourcecache.cpp create mode 100644 src/tests/unit_tests/webresourcecache_tests.cpp diff --git a/.agent/plans/resource_shared_ownership_architecture.md b/.agent/plans/resource_shared_ownership_architecture.md index a9f8d69c7..e8d1ce752 100644 --- a/.agent/plans/resource_shared_ownership_architecture.md +++ b/.agent/plans/resource_shared_ownership_architecture.md @@ -1,6 +1,6 @@ # eepp shared-resource ownership architecture -Status: active implementation baseline; Stage 0 through Stage 5 complete, 2026-07-21. +Status: active implementation baseline; Stage 0 through Stage 6 complete, 2026-07-21. This document freezes the contracts that must be true before the public texture API is changed. The implementation may refine names and small mechanics, but changing an invariant below requires an @@ -752,6 +752,28 @@ Exit criteria: ### Stage 6: WebResourceCache and document leases +Status: complete, 2026-07-21. Each UISceneNode now owns a WebResourceCache document session with +an explicit cache partition and navigation generation. UIWebView advances that session at the +existing navigation boundary. Document, stylesheet, remote font, image, and CSS background image +requests share canonical fragment-free keys that include the partition, request method/body and +headers, resource kind, and image decode options. + +Concurrent requests coalesce into one fetch and one image decode/upload. Subscribers retain their +own document generation, so navigation or destruction removes stale delivery without cancelling a +request needed by another session. Applications can install one cache and explicit partition into +multiple WebViews to share eligible public resources; distinct partitions and content-affecting +headers remain isolated. Scene ResourceScope entries remain an explicit override but fetched Web +resources are retained only by consumers, document leases, and the cache. + +Completed entries use monotonic TTL and LRU timestamps plus a configurable byte budget. Active +document leases are not evicted; the TTL starts when the final document lease is released, allowing +Back/Forward navigation to reuse resources regardless of how long the previous document remained +open. Navigation releases only that session's previous leases, and UIWebView performs throttled +cache maintenance so expired unleased entries are collected even when no new requests complete. Failed loads +retry, redirect/final cookies are delivered only to current subscribers, and image upload is +dispatched through the scene's guarded main-thread resource queue. Common lease lists use inline +small vectors, and completed entries release starter request bodies, headers, and dispatchers. + Implement cache partitions, canonical keys/origins, per-document sessions and leases, in-flight coalescing, per-subscriber generation guards, retries, TTL/LRU, and byte budgets. Integrate WebView navigation at its existing document replacement boundary. @@ -837,8 +859,6 @@ Remove raw-owning `ResourceManager` only when no subclass or consumer depends ## 12. Next implementation deliverable -Stage 2 is complete with the repository-wide TexturePtr API and holder migration while temporary -factory retention preserves existing global lookup behavior. The next coding deliverable is Stage -3: introduce catalogs and Graphics scopes, migrate semantic name lookup out of TextureFactory, move -intentional persistence into catalogs/caches, and then remove the factory's temporary strong -retention. +Stage 7 migrates the remaining ResourceManager families one at a time: fonts and font caches, +themes/icons, shaders/programs, nine-patches, atlases, and any remaining raw-owning manager. The +raw-owning ResourceManager template is removed only after its final consumer is migrated. diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index 0765126c1..38488a760 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -180,6 +180,7 @@ #include #include #include +#include #include #endif diff --git a/include/eepp/ui/uinodedrawable.hpp b/include/eepp/ui/uinodedrawable.hpp index 1ad5aa864..c589eedb7 100644 --- a/include/eepp/ui/uinodedrawable.hpp +++ b/include/eepp/ui/uinodedrawable.hpp @@ -145,9 +145,6 @@ class EE_API UINodeDrawable : public Drawable { Origin mOrigin{ Origin::PaddingBox }; Clip mClip{ Clip::BorderBox }; Attachment mAttachment{ Attachment::Scroll }; - std::shared_ptr> mAsyncDrawableAlive; - Uint64 mRemoteDrawableLoadId{ 0 }; - virtual void onPositionChange(); virtual void onColorFilterChange(); diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index 8aae83e45..8910452bf 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -850,6 +851,21 @@ class EE_API UISceneNode : public SceneNode { Network::CookieManager& getCookieManager() { return mCookieManager; } + const WebResourceCachePtr& getWebResourceCache() const { return mWebResourceCache; } + + UISceneNode* setWebResourceCache( WebResourceCachePtr cache, CachePartitionId partition = 0 ); + + DocumentSessionId getDocumentSessionId() const { return mDocumentSessionId; } + + Uint64 beginDocumentNavigation( const URI& uri ); + + Uint64 getDocumentGeneration() const; + + void requestWebResource( WebResourceRequest request, WebResourceCache::Callback callback ); + + Graphics::TexturePtr requestWebTexture( WebResourceRequest request, + WebResourceCache::Callback callback = {} ); + void invalidateAsyncResourceLoads(); virtual void invalidate( Node* invalidator ); @@ -918,6 +934,8 @@ class EE_API UISceneNode : public SceneNode { std::shared_ptr mAsyncResourceLoadState; Graphics::ResourceScopePtr mResourceScope; DrawableResolver mDrawableResolver; + WebResourceCachePtr mWebResourceCache; + DocumentSessionId mDocumentSessionId{ 0 }; KeyBindings mKeyBindings; std::map mKeyBindingCommands; UnorderedSet mDirtyStyle; diff --git a/include/eepp/ui/uiwebview.hpp b/include/eepp/ui/uiwebview.hpp index b650c4aa8..4308a26b9 100644 --- a/include/eepp/ui/uiwebview.hpp +++ b/include/eepp/ui/uiwebview.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -72,6 +73,10 @@ class EE_API UIWebView : public UIScrollView { UISceneNode* getDocumentSceneNode() const; + const WebResourceCachePtr& getWebResourceCache() const; + + UIWebView* setWebResourceCache( WebResourceCachePtr cache, CachePartitionId partition = 0 ); + void setStyleSheetDefaultMarker( Uint32 marker ); void setUserAgent( const std::string& userAgent ); @@ -117,6 +122,7 @@ class EE_API UIWebView : public UIScrollView { Uint64 mNavigationGeneration{ 0 }; std::string mUserAgent; Time mDefaultTimeout{ Seconds( 30 ) }; + Time mWebResourceCachePruneElapsed; Uint32 mStyleSheetDefaultMarker{ 0 }; void loadURI( URI uri, bool isHistoryNav ); diff --git a/include/eepp/ui/webresourcecache.hpp b/include/eepp/ui/webresourcecache.hpp new file mode 100644 index 000000000..e7d0dd356 --- /dev/null +++ b/include/eepp/ui/webresourcecache.hpp @@ -0,0 +1,302 @@ +#ifndef EE_UI_WEBRESOURCECACHE_HPP +#define EE_UI_WEBRESOURCECACHE_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace EE { namespace UI { + +/** Shared ownership handle for a WebResourceCache. + * + * A cache can be installed into more than one UIWebView. Sharing the cache does not by itself make + * all resources visible between those views: sessions must also use the same CachePartitionId. */ +using WebResourceCachePtr = std::shared_ptr; + +/** Identifies one document consumer of a WebResourceCache. + * + * UISceneNode creates a session for its current document. A session tracks the current navigation + * generation and the cache entries leased by that document. IDs are process-wide and opaque. */ +using DocumentSessionId = Uint64; + +/** Identifies a cache sharing and privacy boundary. + * + * Sessions in the same partition may reuse and coalesce resources. Sessions in different + * partitions never share entries, even when requesting the same URI. A partition should therefore + * represent one intentionally shared HTTP state, normally one cookie jar/authentication context. + * + * For example, browser tabs that share cookies should use the same WebResourceCache and + * CachePartitionId. A private tab or a view logged into a different account must use another + * partition. Passing zero to createSession() creates a new private partition automatically. + * Partition IDs are process-wide and opaque. + * + * @code + * auto cache = WebResourceCache::New(); + * auto normalProfile = cache->createPartition(); + * firstTab->setWebResourceCache( cache, normalProfile ); + * secondTab->setWebResourceCache( cache, normalProfile ); // May reuse firstTab resources. + * privateTab->setWebResourceCache( cache, cache->createPartition() ); // Fully isolated entries. + * @endcode */ +using CachePartitionId = Uint64; + +/** Canonical network origin used to describe a document navigation. + * + * Origins compare scheme, host, and port. Consequently HTTP and HTTPS origins are distinct, as are + * equal hosts using different ports. Paths, queries, and fragments are not part of an origin. */ +struct EE_API WebOriginKey { + /** Lowercase URI scheme, such as "http" or "https". */ + std::string scheme; + /** Lowercase host name. */ + std::string host; + /** URI port, or the URI implementation's default/empty port value. */ + Uint16 port{ 0 }; + + /** @return True when no origin components have been assigned. */ + bool empty() const { return scheme.empty() && host.empty() && port == 0; } + /** @return True when both values describe the same scheme, host, and port. */ + bool operator==( const WebOriginKey& other ) const; + /** @return True when at least one origin component differs. */ + bool operator!=( const WebOriginKey& other ) const { return !( *this == other ); } + + /** Creates an origin key from a URI, normalizing the scheme and host to lowercase. */ + static WebOriginKey fromURI( const Network::URI& uri ); +}; + +/** Semantic type of a cached web resource. + * + * The kind is part of cache identity, so one URI requested as a document and as a stylesheet uses + * distinct entries. Images additionally carry texture decode options in WebResourceRequest. */ +enum class WebResourceKind : Uint8 { + /** Top-level HTML or another document response. */ + Document, + /** CSS stylesheet response. */ + StyleSheet, + /** Downloadable font response. */ + Font, + /** Image response decoded into a Graphics::Texture. */ + Image +}; + +/** Current lifecycle state of a cache entry. */ +enum class WebResourceLoadState : Uint8 { + /** Entry exists but has not started loading. */ + Empty, + /** One fetch is active; additional subscribers join it. */ + Loading, + /** Resource completed successfully and is available for reuse. */ + Ready, + /** Last fetch failed and remains subject to the retry delay. */ + Failed, + /** Entry was cleared and any late fetch completion will be ignored. */ + Cancelled +}; + +/** Complete input required to identify and fetch one web resource variant. + * + * Cache identity includes the partition, canonical URI, resource kind, method, non-navigation + * headers, body, and image decode options. Cookie and Referer are deliberately excluded from entry + * identity: the partition isolates cookie contexts, while these two values naturally change during + * navigation. They are still sent with the HTTP request. + * + * Transport policy such as timeout, proxy, and completionDispatcher controls a fetch but does not + * describe the resulting resource and is therefore not part of cache identity. */ +struct EE_API WebResourceRequest { + /** Dispatches a completion function to the thread on which it is safe to finalize a resource. + */ + using CompletionDispatcher = std::function )>; + + /** Absolute URI to fetch. Fragments are removed and the URI is normalized for cache lookup. */ + Network::URI uri; + /** Semantic resource type. */ + WebResourceKind kind{ WebResourceKind::Document }; + /** HTTP method. The method is part of cache identity. */ + Network::Http::Request::Method method{ Network::Http::Request::Method::Get }; + /** HTTP request headers. See the type documentation for cache-key rules. */ + Network::Http::Request::FieldTable headers; + /** HTTP request body. The complete body is part of cache identity. */ + std::string body; + /** Maximum duration allowed for the HTTP request. */ + System::Time timeout{ System::Seconds( 5 ) }; + /** Whether HTTPS certificates must be validated. */ + bool validateCertificate{ true }; + /** Optional HTTP proxy URI. An empty URI uses a direct connection. */ + Network::URI proxy; + /** Whether HTTP redirects should be followed. */ + bool followRedirect{ true }; + /** Image-only texture wrapping policy. True selects ClampToEdge. */ + bool clampToEdge{ true }; + /** Image-only option controlling mipmap generation. */ + bool mipmaps{ false }; + /** Image-only option requesting texture compression. */ + bool compressTexture{ false }; + /** Image-only SVG rasterization scale. */ + Float svgScale{ 1.f }; + /** Image-only completion dispatcher. + * + * Image decode/upload completion must execute where a graphics context is current. This policy + * is not part of cache identity and only the request that starts a coalesced load supplies it. + */ + CompletionDispatcher completionDispatcher; +}; + +/** Result delivered to a WebResourceCache subscriber. + * + * Data resources set data, while image resources set texture. A texture request may already have + * returned the same TexturePtr as a transparent placeholder before this result is delivered. */ +struct EE_API WebResourceResult { + /** True when the request completed and produced a usable resource. */ + bool success{ false }; + /** Final cache-entry state observed by this completion. */ + WebResourceLoadState state{ WebResourceLoadState::Empty }; + /** Numeric HTTP response status. */ + int status{ 0 }; + /** Failure description. Empty for successful results. */ + std::string error; + /** Set-Cookie value from the final response, or from its redirect chain when applicable. */ + std::string setCookie; + /** Shared immutable response body for documents, stylesheets, and fonts. */ + std::shared_ptr data; + /** Shared texture for image resources. */ + Graphics::TexturePtr texture; +}; + +/** Shared HTTP-backed document resource cache. + * + * The cache separates three concepts: + * - A partition defines which HTTP/cookie context may share entries. + * - A session represents one active document using entries from that partition. + * - A navigation generation prevents an obsolete document from receiving asynchronous results. + * + * Entries are strongly retained by the cache after their final document lease is released. Their + * TTL starts at that release point, allowing Back/Forward navigation to reuse them. Expired entries + * are removed by prune(); UIWebView invokes it periodically. A byte budget additionally evicts the + * least-recently-used unleased entries. Active leases and in-flight loads are not evicted. + * + * Requests for the same key are coalesced into one HTTP operation. Each current subscriber receives + * the result, while callbacks belonging to obsolete navigation generations are discarded. Public + * operations are internally synchronized. Subscriber callbacks run without the cache mutex held. */ +class EE_API WebResourceCache : public std::enable_shared_from_this { + public: + /** Receives the completed result of a data or texture request. */ + using Callback = std::function; + + /** @return A newly allocated shared web-resource cache. */ + static WebResourceCachePtr New(); + + /** Creates an empty cache with a 30-second TTL, 1-second retry delay, and 64 MiB budget. */ + WebResourceCache(); + /** Cancels cache delivery and releases all retained entries and sessions. */ + ~WebResourceCache(); + + /** Allocates a new process-wide partition identifier. + * @return An opaque, non-zero partition ID. */ + CachePartitionId createPartition(); + + /** Creates a document session. + * @param partition Partition to join. Zero creates a new private partition. + * @return An opaque, non-zero document session ID. */ + DocumentSessionId createSession( CachePartitionId partition = 0 ); + + /** Destroys a session and releases all entries leased by it. + * + * Releasing the final lease starts each entry's TTL; it does not normally erase entries + * immediately. Pending callbacks for the destroyed session will not be delivered. */ + void destroySession( DocumentSessionId session ); + + /** Begins a new document navigation in an existing session. + * + * Previous document leases are released and the generation is incremented. Pending subscribers + * from older generations become stale, but shared in-flight requests continue for other current + * subscribers. + * @param session Session performing the navigation. + * @param uri Destination document URI, used to record its origin. + * @return The new generation, or zero when the session does not exist. */ + Uint64 beginNavigation( DocumentSessionId session, const Network::URI& uri ); + + /** @return The current generation for a session, or zero when it does not exist. */ + Uint64 getSessionGeneration( DocumentSessionId session ) const; + /** @return The partition used by a session, or zero when it does not exist. */ + CachePartitionId getSessionPartition( DocumentSessionId session ) const; + + /** Requests a document, stylesheet, font, or other non-texture response body. + * + * A ready cache hit may invoke callback synchronously. A miss starts or joins an asynchronous + * fetch. The callback is omitted when the session/generation is stale. + * @param session Requesting document session. + * @param generation Generation returned by beginNavigation(). + * @param request Resource and transport description. + * @param callback Optional completion callback. */ + void requestData( DocumentSessionId session, Uint64 generation, WebResourceRequest request, + Callback callback ); + + /** Requests an image as a shared texture. + * + * On a miss, this creates and returns a transparent placeholder immediately. The same + * TexturePtr is populated after download and decode. A ready hit returns the existing texture + * without a new HTTP request. This function must be called where texture creation is valid, and + * the request must provide an appropriate completionDispatcher for asynchronous GPU upload. + * @return The cached/new texture, or null for an invalid or stale session. */ + Graphics::TexturePtr requestTexture( DocumentSessionId session, Uint64 generation, + WebResourceRequest request, Callback callback = {} ); + + /** Sets the retention duration used when an entry's final document lease is released. */ + void setTTL( System::Time ttl ); + /** @return The current post-lease retention duration. */ + System::Time getTTL() const; + /** Sets how long a failed entry suppresses another fetch attempt. */ + void setRetryDelay( System::Time delay ); + /** @return The current failed-request retry delay. */ + System::Time getRetryDelay() const; + /** Sets the maximum retained response/texture byte estimate. + * + * Applying a smaller budget immediately evicts least-recently-used unleased entries where + * possible. Active entries may temporarily make retained bytes exceed the budget. */ + void setByteBudget( std::size_t bytes ); + /** @return The configured retained-byte budget. */ + std::size_t getByteBudget() const; + /** @return Estimated bytes retained by ready data bodies and decoded image textures. */ + std::size_t getRetainedBytes() const; + /** @return Number of cache entries in all states and partitions. */ + std::size_t getEntryCount() const; + /** @return Number of HTTP requests currently owned by the cache. */ + std::size_t getInFlightCount() const; + + /** Removes expired entries and enforces the byte budget. + * + * Active document leases and loading entries are preserved. UIWebView calls this periodically; + * other cache owners should provide their own maintenance point. */ + void prune(); + + /** Removes every entry immediately and suppresses delivery from outstanding fetches. + * + * Sessions remain valid but no longer lease entries. This does not cancel the underlying HTTP + * operation; a late completion is ignored because its entry no longer exists. */ + void clear(); + + /** Completion supplied to a custom Fetcher. */ + using FetchCompletion = std::function; + /** Custom fetch implementation, primarily intended for deterministic tests. + * + * The fetcher must eventually invoke FetchCompletion exactly once. Production uses + * Http::requestAsync when no custom fetcher is installed. */ + using Fetcher = std::function; + + /** Installs or clears a custom HTTP fetch implementation. */ + void setFetcher( Fetcher fetcher ); + + private: + /** Private implementation containing synchronized sessions and cache entries. */ + struct Impl; + /** Exclusive implementation state. */ + std::unique_ptr mImpl; +}; + +}} // namespace EE::UI + +#endif diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 46d4890f7..3f333207e 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -58,6 +58,7 @@ ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp ../../include/eepp/ui/drawableresolver.hpp +../../include/eepp/ui/webresourcecache.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontfamily.hpp @@ -556,6 +557,7 @@ ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp ../../src/eepp/ui/drawableresolver.cpp +../../src/eepp/ui/webresourcecache.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontfamily.cpp diff --git a/projects/macos/ee.files b/projects/macos/ee.files index a320ffa16..a2525b6d2 100644 --- a/projects/macos/ee.files +++ b/projects/macos/ee.files @@ -58,6 +58,7 @@ ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp ../../include/eepp/ui/drawableresolver.hpp +../../include/eepp/ui/webresourcecache.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontfamily.hpp @@ -547,6 +548,7 @@ ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp ../../src/eepp/ui/drawableresolver.cpp +../../src/eepp/ui/webresourcecache.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontfamily.cpp diff --git a/projects/windows/ee.files b/projects/windows/ee.files index 1991069ec..f3cdd029c 100644 --- a/projects/windows/ee.files +++ b/projects/windows/ee.files @@ -57,6 +57,7 @@ ../../include/eepp/graphics/drawable.hpp ../../include/eepp/graphics/drawableresource.hpp ../../include/eepp/ui/drawableresolver.hpp +../../include/eepp/ui/webresourcecache.hpp ../../include/eepp/graphics/fontbmfont.hpp ../../include/eepp/graphics/font.hpp ../../include/eepp/graphics/fontmanager.hpp @@ -539,6 +540,7 @@ ../../src/eepp/graphics/drawablegroup.cpp ../../src/eepp/graphics/drawableresource.cpp ../../src/eepp/ui/drawableresolver.cpp +../../src/eepp/ui/webresourcecache.cpp ../../src/eepp/graphics/fontbmfont.cpp ../../src/eepp/graphics/font.cpp ../../src/eepp/graphics/fontmanager.cpp diff --git a/src/eepp/ui/drawableresolver.cpp b/src/eepp/ui/drawableresolver.cpp index 2b5e8d35a..d287ad94f 100644 --- a/src/eepp/ui/drawableresolver.cpp +++ b/src/eepp/ui/drawableresolver.cpp @@ -103,7 +103,21 @@ DrawablePtr DrawableResolver::resolve( const std::string& name, bool firstSearch drawable = texture ? texture->clone() : DrawablePtr{}; } else if ( String::startsWith( name, "http://" ) || String::startsWith( name, "https://" ) ) { TexturePtr texture = resourceScope.findTexture( name ); - if ( !texture && Engine::instance()->isSharedGLContextEnabled() ) { + if ( mSceneNode && Engine::instance()->isSharedGLContextEnabled() ) { + if ( !texture ) { + WebResourceRequest request; + request.uri = URI( name ); + request.kind = WebResourceKind::Image; + texture = mSceneNode->requestWebTexture( + std::move( request ), [name]( const WebResourceResult& result ) { + if ( !result.success ) + Log::debug( "DrawableResolver::resolve: could not download image: %s. " + "Error: %d\n%s", + name, result.status, result.error ); + } ); + } + } + if ( !mSceneNode && !texture && Engine::instance()->isSharedGLContextEnabled() ) { texture = TextureFactory::instance()->createEmptyTexture( 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, name ); diff --git a/src/eepp/ui/uiimage.cpp b/src/eepp/ui/uiimage.cpp index 36db9eaef..fdeb8523b 100644 --- a/src/eepp/ui/uiimage.cpp +++ b/src/eepp/ui/uiimage.cpp @@ -64,10 +64,7 @@ UIImage* UIImage::NewWithTag( const std::string& tag ) { } UIImage::UIImage( const std::string& tag ) : - UIWidget( tag ), - mScaleType( UIScaleType::None ), - mColor(), - mAlignOffset( 0, 0 ) { + UIWidget( tag ), mScaleType( UIScaleType::None ), mColor(), mAlignOffset( 0, 0 ) { mFlags |= UI_AUTO_SIZE; applyDefaultTheme(); @@ -379,65 +376,38 @@ void UIImage::loadRemoteDrawable( const Network::URI& uri ) { return; std::string url = uri.toString(); - ResourceScopePtr resourceScope = scene->getResourceScope(); - if ( TexturePtr texture = resourceScope->findTexture( url ) ) { - TextureDrawable* textureDrawable = + if ( TexturePtr texture = scene->getResourceScope()->findTexture( url ) ) { + TextureDrawable* current = mDrawable && mDrawable->getDrawableType() == Drawable::TEXTUREDRAWABLE ? static_cast( mDrawable.get() ) : nullptr; - if ( !textureDrawable || textureDrawable->getTexture() != texture ) { + if ( !current || current->getTexture() != texture ) { ++mRemoteImageLoadId; setDrawable( std::move( texture ) ); } return; } - - auto resourceState = scene->getAsyncResourceLoadState(); - Uint64 resourceGeneration = - resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; - Uint64 loadId = ++mRemoteImageLoadId; - if ( !mAsyncImageAlive ) - mAsyncImageAlive = std::make_shared>( true ); - auto alive = mAsyncImageAlive; - TexturePtr texture = TextureFactory::instance()->createEmptyTexture( - 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); - if ( texture ) { - resourceScope->publishLocal( url, texture ); - setDrawable( texture ); - } - - Http::Request::FieldTable headers; - if ( !scene->getReferer().empty() ) - headers["referer"] = scene->getReferer().toString(); - Http::getAsync( - [resourceState, resourceGeneration, alive, loadId, texture, url = std::move( url ), - this]( const Http&, Http::Request&, Http::Response& response ) { - if ( !UISceneNode::isAsyncResourceLoadCurrent( resourceState, resourceGeneration ) || - !alive || !alive->load( std::memory_order_acquire ) || texture == nullptr ) - return; - - if ( response.isOK() && !response.getBody().empty() ) { - std::string imageData( response.getBody() ); - UISceneNode::runAsyncResourceOnMainThread( - resourceState, resourceGeneration, - [alive, loadId, texture, imageData = std::move( imageData ), - this]( UISceneNode* ) mutable { - if ( !alive || !alive->load( std::memory_order_acquire ) || - loadId != mRemoteImageLoadId ) - return; - - Image image( reinterpret_cast( imageData.data() ), - imageData.size() ); - if ( image.getPixels() != nullptr ) - texture->replace( &image ); - } ); - } else { + WebResourceRequest request; + request.uri = uri; + request.kind = WebResourceKind::Image; + request.proxy = Http::getEnvProxyURI(); + TexturePtr texture = scene->requestWebTexture( + std::move( request ), [url = std::move( url )]( const WebResourceResult& result ) { + if ( !result.success ) Log::debug( "UIImage::loadRemoteDrawable: could not download image: %s. Error: " "%d\n%s", - url, response.getStatus(), response.getBody() ); - } - }, - uri, Seconds( 5 ), {}, headers, "", true, Http::getEnvProxyURI() ); + url, result.status, result.error ); + } ); + if ( texture ) { + TextureDrawable* current = + mDrawable && mDrawable->getDrawableType() == Drawable::TEXTUREDRAWABLE + ? static_cast( mDrawable.get() ) + : nullptr; + if ( !current || current->getTexture() != texture ) { + ++mRemoteImageLoadId; + setDrawable( std::move( texture ) ); + } + } } void UIImage::onSizeChange() { diff --git a/src/eepp/ui/uinodedrawable.cpp b/src/eepp/ui/uinodedrawable.cpp index b6cecd7bc..5f3b5f7ce 100644 --- a/src/eepp/ui/uinodedrawable.cpp +++ b/src/eepp/ui/uinodedrawable.cpp @@ -418,9 +418,6 @@ UINodeDrawable::LayerDrawable::LayerDrawable( UINodeDrawable* container ) : mAttachment( Attachment::Scroll ) {} UINodeDrawable::LayerDrawable::~LayerDrawable() { - if ( mAsyncDrawableAlive ) - mAsyncDrawableAlive->store( false, std::memory_order_release ); - mResourceChangeConnection.disconnect(); } @@ -677,65 +674,34 @@ bool UINodeDrawable::LayerDrawable::loadRemoteDrawable( const std::string& value return true; std::string url = uri.toString(); - ResourceScopePtr resourceScope = scene->getResourceScope(); - if ( TexturePtr texture = resourceScope->findTexture( url ) ) { - TextureDrawable* textureDrawable = + if ( TexturePtr texture = scene->getResourceScope()->findTexture( url ) ) { + TextureDrawable* current = mDrawable && mDrawable->getDrawableType() == Drawable::TEXTUREDRAWABLE ? static_cast( mDrawable.get() ) : nullptr; - if ( !textureDrawable || textureDrawable->getTexture() != texture ) { - ++mRemoteDrawableLoadId; + if ( !current || current->getTexture() != texture ) setDrawable( std::move( texture ) ); - } return true; } - - auto resourceState = scene->getAsyncResourceLoadState(); - Uint64 resourceGeneration = - resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; - Uint64 loadId = ++mRemoteDrawableLoadId; - if ( !mAsyncDrawableAlive ) - mAsyncDrawableAlive = std::make_shared>( true ); - auto alive = mAsyncDrawableAlive; - TexturePtr texture = TextureFactory::instance()->createEmptyTexture( - 1, 1, 4, Color::Transparent, false, Texture::ClampMode::ClampToEdge, false, false, url ); - if ( texture ) { - resourceScope->publishLocal( url, texture ); - setDrawable( texture ); - } - - Http::Request::FieldTable headers; - if ( !scene->getReferer().empty() ) - headers["referer"] = scene->getReferer().toString(); - Http::getAsync( - [resourceState, resourceGeneration, alive, loadId, texture, url = std::move( url ), - this]( const Http&, Http::Request&, Http::Response& response ) { - if ( !UISceneNode::isAsyncResourceLoadCurrent( resourceState, resourceGeneration ) || - !alive || !alive->load( std::memory_order_acquire ) || texture == nullptr ) - return; - - if ( response.isOK() && !response.getBody().empty() ) { - std::string imageData( response.getBody() ); - UISceneNode::runAsyncResourceOnMainThread( - resourceState, resourceGeneration, - [alive, loadId, texture, imageData = std::move( imageData ), - this]( UISceneNode* ) mutable { - if ( !alive || !alive->load( std::memory_order_acquire ) || - loadId != mRemoteDrawableLoadId ) - return; - - Image image( reinterpret_cast( imageData.data() ), - imageData.size() ); - if ( image.getPixels() != nullptr ) - texture->replace( &image ); - } ); - } else { + WebResourceRequest request; + request.uri = uri; + request.kind = WebResourceKind::Image; + request.proxy = Http::getEnvProxyURI(); + TexturePtr texture = scene->requestWebTexture( + std::move( request ), [url = std::move( url )]( const WebResourceResult& result ) { + if ( !result.success ) Log::debug( "UINodeDrawable::LayerDrawable::loadRemoteDrawable: could not " "download image: %s. Error: %d\n%s", - url, response.getStatus(), response.getBody() ); - } - }, - uri, Seconds( 5 ), {}, headers, "", true, Http::getEnvProxyURI() ); + url, result.status, result.error ); + } ); + if ( texture ) { + TextureDrawable* current = + mDrawable && mDrawable->getDrawableType() == Drawable::TEXTUREDRAWABLE + ? static_cast( mDrawable.get() ) + : nullptr; + if ( !current || current->getTexture() != texture ) + setDrawable( std::move( texture ) ); + } return true; } diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 03b921e93..834a59631 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -143,6 +143,7 @@ UISceneNode::UISceneNode( EE::Window::Window* window ) : mAsyncResourceLoadState( std::make_shared() ), mResourceScope( ResourceScope::New() ), mDrawableResolver( *this ), + mWebResourceCache( WebResourceCache::New() ), mKeyBindings( mWindow->getInput() ) { // Reset size since the SceneNode already set it but needs to set the size from zero to emit // the required events to its children. @@ -160,6 +161,7 @@ UISceneNode::UISceneNode( EE::Window::Window* window ) : mRoot->setParent( this )->setPosition( 0, 0 )->setId( "uiscenenode_root_node" ); mRoot->enableReportSizeChangeToChildren(); mAsyncResourceLoadState->owner.store( this, std::memory_order_release ); + mDocumentSessionId = mWebResourceCache->createSession(); resizeNode( mWindow ); } @@ -175,6 +177,8 @@ UISceneNode::~UISceneNode() { mHostUISceneNode->unregisterChildUISceneNode( this ); clearFontFaces(); + if ( mWebResourceCache && mDocumentSessionId ) + mWebResourceCache->destroySession( mDocumentSessionId ); eeSAFE_DELETE( mUIThemeManager ); eeSAFE_DELETE( mUIIconThemeManager ); @@ -762,6 +766,94 @@ UISceneNode* UISceneNode::setResourceScope( ResourceScopePtr resourceScope ) { return this; } +UISceneNode* UISceneNode::setWebResourceCache( WebResourceCachePtr cache, + CachePartitionId partition ) { + if ( !cache ) + cache = WebResourceCache::New(); + if ( cache == mWebResourceCache && + ( partition == 0 || partition == cache->getSessionPartition( mDocumentSessionId ) ) ) + return this; + if ( mWebResourceCache && mDocumentSessionId ) + mWebResourceCache->destroySession( mDocumentSessionId ); + mWebResourceCache = std::move( cache ); + mDocumentSessionId = mWebResourceCache->createSession( partition ); + return this; +} + +Uint64 UISceneNode::beginDocumentNavigation( const URI& uri ) { + return mWebResourceCache && mDocumentSessionId + ? mWebResourceCache->beginNavigation( mDocumentSessionId, uri ) + : 0; +} + +Uint64 UISceneNode::getDocumentGeneration() const { + return mWebResourceCache && mDocumentSessionId + ? mWebResourceCache->getSessionGeneration( mDocumentSessionId ) + : 0; +} + +void UISceneNode::requestWebResource( WebResourceRequest request, + WebResourceCache::Callback callback ) { + if ( !mWebResourceCache || !mDocumentSessionId ) + return; + if ( !mReferer.empty() ) + request.headers.emplace( "referer", mReferer.toString() ); + std::string cookie = mCookieManager.getCookieHeader( request.uri.getAuthority() ); + if ( !cookie.empty() ) + request.headers["Cookie"] = std::move( cookie ); + auto resourceState = mAsyncResourceLoadState; + Uint64 resourceGeneration = + resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; + auto wrapped = [resourceState, resourceGeneration, callback = std::move( callback ), + authority = request.uri.getAuthority()]( const WebResourceResult& result ) { + if ( !UISceneNode::isAsyncResourceLoadCurrent( resourceState, resourceGeneration ) ) + return; + UISceneNode* scene = resourceState->owner.load( std::memory_order_acquire ); + if ( !scene ) + return; + if ( !result.setCookie.empty() ) + scene->mCookieManager.storeCookiesFromHeader( authority, result.setCookie ); + if ( callback ) + callback( result ); + }; + mWebResourceCache->requestData( mDocumentSessionId, getDocumentGeneration(), + std::move( request ), std::move( wrapped ) ); +} + +TexturePtr UISceneNode::requestWebTexture( WebResourceRequest request, + WebResourceCache::Callback callback ) { + if ( !mWebResourceCache || !mDocumentSessionId ) + return {}; + if ( !mReferer.empty() ) + request.headers.emplace( "referer", mReferer.toString() ); + std::string cookie = mCookieManager.getCookieHeader( request.uri.getAuthority() ); + if ( !cookie.empty() ) + request.headers["Cookie"] = std::move( cookie ); + auto resourceState = mAsyncResourceLoadState; + Uint64 resourceGeneration = + resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; + request.completionDispatcher = [resourceState, + resourceGeneration]( std::function completion ) { + UISceneNode::runAsyncResourceOnMainThread( + resourceState, resourceGeneration, + [completion = std::move( completion )]( UISceneNode* ) { completion(); } ); + }; + auto wrapped = [resourceState, resourceGeneration, callback = std::move( callback ), + authority = request.uri.getAuthority()]( const WebResourceResult& result ) { + if ( !UISceneNode::isAsyncResourceLoadCurrent( resourceState, resourceGeneration ) ) + return; + UISceneNode* scene = resourceState->owner.load( std::memory_order_acquire ); + if ( !scene ) + return; + if ( !result.setCookie.empty() ) + scene->mCookieManager.storeCookiesFromHeader( authority, result.setCookie ); + if ( callback ) + callback( result ); + }; + return mWebResourceCache->requestTexture( mDocumentSessionId, getDocumentGeneration(), + std::move( request ), std::move( wrapped ) ); +} + static std::string getErrorContext( size_t offset, std::string_view content ) { static constexpr std::size_t CONTEXT_LENGTH = 50; std::size_t minVal = offset >= CONTEXT_LENGTH ? offset - CONTEXT_LENGTH : 0; @@ -1632,47 +1724,48 @@ void UISceneNode::loadFontFaces( const StyleSheetStyleVector& styles, URI baseUR auto resourceState = mAsyncResourceLoadState; Uint64 resourceGeneration = resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0; - Http::getAsync( - [resourceState, resourceGeneration, internalFontName, authorFamily, fontStyle, - fontWeight, path]( const Http&, Http::Request&, Http::Response& response ) { - if ( !UISceneNode::isAsyncResourceLoadCurrent( resourceState, - resourceGeneration ) ) - return; + WebResourceRequest request; + request.uri = URI( path ); + request.kind = WebResourceKind::Font; + request.timeout = Seconds( 5 ); + requestWebResource( std::move( request ), [resourceState, resourceGeneration, + internalFontName, authorFamily, fontStyle, + fontWeight, + path]( const WebResourceResult& result ) { + if ( !UISceneNode::isAsyncResourceLoadCurrent( resourceState, resourceGeneration ) ) + return; - if ( response.isOK() && !response.getBody().empty() ) { - std::string fontData( response.getBody() ); - UISceneNode::runAsyncResourceOnMainThread( - resourceState, resourceGeneration, - [fontData = std::move( fontData ), internalFontName, authorFamily, - fontStyle, fontWeight]( UISceneNode* scene ) mutable { - FontTrueType* font = FontTrueType::New( internalFontName ); - if ( font->loadFromMemory( &fontData[0], fontData.size() ) && - font->loaded() ) { - font->setVariableFontWeight( fontWeight ); - scene->registerFontFaceAlias( authorFamily, fontStyle, - fontWeight, font ); - scene->mFontFaces.push_back( font ); - if ( scene->mRoot ) - scene->mRoot->reloadFontFamily(); - } else { - eeSAFE_DELETE( font ); - } - } ); - } else { - UISceneNode::runAsyncResourceOnMainThread( - resourceState, resourceGeneration, - [internalFontName, path, status = response.getStatus(), - statusDescription = - std::string( response.getStatusDescription() )]( UISceneNode* ) { - Log::error( "UISceneNode::loadFontFaces: Failed to load font " - "\"%s\", from: %s. Request response status code: %d " - "(%s)", - internalFontName, path, status, - statusDescription.c_str() ); - } ); - } - }, - URI( path ), Seconds( 5 ) ); + if ( result.success && result.data && !result.data->empty() ) { + std::string fontData( *result.data ); + UISceneNode::runAsyncResourceOnMainThread( + resourceState, resourceGeneration, + [fontData = std::move( fontData ), internalFontName, authorFamily, + fontStyle, fontWeight]( UISceneNode* scene ) mutable { + FontTrueType* font = FontTrueType::New( internalFontName ); + if ( font->loadFromMemory( &fontData[0], fontData.size() ) && + font->loaded() ) { + font->setVariableFontWeight( fontWeight ); + scene->registerFontFaceAlias( authorFamily, fontStyle, fontWeight, + font ); + scene->mFontFaces.push_back( font ); + if ( scene->mRoot ) + scene->mRoot->reloadFontFamily(); + } else { + eeSAFE_DELETE( font ); + } + } ); + } else { + UISceneNode::runAsyncResourceOnMainThread( + resourceState, resourceGeneration, + [internalFontName, path, status = result.status, + statusDescription = result.error]( UISceneNode* ) { + Log::error( "UISceneNode::loadFontFaces: Failed to load font " + "\"%s\", from: %s. Request response status code: %d " + "(%s)", + internalFontName, path, status, statusDescription.c_str() ); + } ); + } + } ); } else if ( VFS::instance()->fileExists( path ) ) { FontTrueType* font = FontTrueType::New( makeInternalFontName( authorFamily, fontStyle, fontWeight ) ); @@ -1786,26 +1879,27 @@ void UISceneNode::loadCSS( URI uri, std::optional