diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index c17148a35..b11e6ee6f 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -56,6 +56,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp ${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inputhandler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/item_visuals_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/keycode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/localplayer.cpp diff --git a/src/client/client.cpp b/src/client/client.cpp index 77853b535..cf19d7e58 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -53,6 +53,7 @@ #include "translation.h" #include "content/mod_configuration.h" #include "mapnode.h" +#include "item_visuals_manager.h" extern gui::IGUIEnvironment* guienv; @@ -95,6 +96,7 @@ Client::Client( ISoundManager *sound, MtEventManager *event, RenderingEngine *rendering_engine, + ItemVisualsManager *item_visuals_manager, ELoginRegister allow_login_or_register ): m_tsrc(tsrc), @@ -104,6 +106,7 @@ Client::Client( m_sound(sound), m_event(event), m_rendering_engine(rendering_engine), + m_item_visuals_manager(item_visuals_manager), m_mesh_update_manager(std::make_unique(this)), m_env( make_irr(this, rendering_engine, control, 666), @@ -346,6 +349,8 @@ Client::~Client() // cleanup 3d model meshes on client shutdown m_rendering_engine->cleanupMeshCache(); + m_item_visuals_manager->clear(); + guiScalingCacheClear(); delete m_minimap; diff --git a/src/client/client.h b/src/client/client.h index d9cb7c6af..12625f24e 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -55,6 +55,7 @@ struct MeshMakeData; struct MinimapMapblock; struct PlayerControl; struct PointedThing; +struct ItemVisualsManager; namespace con { class IConnection; @@ -118,6 +119,7 @@ public: ISoundManager *sound, MtEventManager *event, RenderingEngine *rendering_engine, + ItemVisualsManager *item_visuals, ELoginRegister allow_login_or_register ); @@ -383,6 +385,8 @@ public: const std::string* getModFile(std::string filename); ModStorageDatabase *getModStorageDatabase() override { return m_mod_storage_database; } + ItemVisualsManager *getItemVisualsManager() { return m_item_visuals_manager; } + // Migrates away old files-based mod storage if necessary void migrateModStorage(); @@ -480,6 +484,7 @@ private: ISoundManager *m_sound; MtEventManager *m_event; RenderingEngine *m_rendering_engine; + ItemVisualsManager *m_item_visuals_manager; std::unique_ptr m_mesh_update_manager; diff --git a/src/client/game.cpp b/src/client/game.cpp index 79889e775..ee6cd0af4 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -60,6 +60,7 @@ #include "clientdynamicinfo.h" #include #include "util/tracy_wrapper.h" +#include "item_visuals_manager.h" #if USE_SOUND #include "client/sound/sound_openal.h" @@ -692,6 +693,7 @@ private: // When created, these will be filled with data received from the server IWritableItemDefManager *itemdef_manager = nullptr; NodeDefManager *nodedef_manager = nullptr; + std::unique_ptr m_item_visuals_manager; std::unique_ptr sound_manager; SoundMaker *soundmaker = nullptr; @@ -1125,6 +1127,8 @@ bool Game::init( itemdef_manager = createItemDefManager(); nodedef_manager = createNodeDefManager(); + m_item_visuals_manager = std::make_unique(); + eventmgr = new EventManager(); quicktune = new QuicktuneShortcutter(); @@ -1443,6 +1447,7 @@ bool Game::connectToServer(const GameStartData &start_data, *draw_control, texture_src, shader_src, itemdef_manager, nodedef_manager, sound_manager.get(), eventmgr, m_rendering_engine, + m_item_visuals_manager.get(), start_data.allow_login_or_register); } catch (const BaseException &e) { *error_message = fmtgettext("Error creating client: %s", e.what()); diff --git a/src/client/item_visuals_manager.cpp b/src/client/item_visuals_manager.cpp new file mode 100644 index 000000000..7503d496b --- /dev/null +++ b/src/client/item_visuals_manager.cpp @@ -0,0 +1,102 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 cx384 + +#include "item_visuals_manager.h" + +#include "mesh.h" +#include "client.h" +#include "texturesource.h" +#include "itemdef.h" +#include "inventory.h" + +ItemVisualsManager::ItemVisuals::~ItemVisuals() { + if (wield_mesh.mesh) + wield_mesh.mesh->drop(); +} + +ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const ItemStack &item, + Client *client) const +{ + // This is not thread-safe + sanity_check(std::this_thread::get_id() == m_main_thread); + + IItemDefManager *idef = client->idef(); + + const ItemDefinition &def = item.getDefinition(idef); + std::string inventory_image = item.getInventoryImage(idef); + std::string inventory_overlay = item.getInventoryOverlay(idef); + std::string cache_key = def.name; + if (!inventory_image.empty()) + cache_key += "/" + inventory_image; + if (!inventory_overlay.empty()) + cache_key += ":" + inventory_overlay; + + // Skip if already in cache + auto it = m_cached_item_visuals.find(cache_key); + if (it != m_cached_item_visuals.end()) + return it->second.get(); + + infostream << "Lazily creating item texture and mesh for \"" + << cache_key << "\"" << std::endl; + + ITextureSource *tsrc = client->getTextureSource(); + + // Create new ItemVisuals + auto cc = std::make_unique(); + + cc->inventory_texture = NULL; + if (!inventory_image.empty()) + cc->inventory_texture = tsrc->getTexture(inventory_image); + getItemMesh(client, item, &(cc->wield_mesh)); + + cc->palette = tsrc->getPalette(def.palette_image); + + // Put in cache + ItemVisuals *ptr = cc.get(); + m_cached_item_visuals[cache_key] = std::move(cc); + return ptr; +} + +video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item, + Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv) + return nullptr; + return iv->inventory_texture; +} + +ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv) + return nullptr; + return &(iv->wield_mesh); +} + +Palette* ItemVisualsManager::getPalette(const ItemStack &item, Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv) + return nullptr; + return iv->palette; +} + +video::SColor ItemVisualsManager::getItemstackColor(const ItemStack &stack, + Client *client) const +{ + // Look for direct color definition + const std::string &colorstring = stack.metadata.getString("color", 0); + video::SColor directcolor; + if (!colorstring.empty() && parseColorString(colorstring, directcolor, true)) + return directcolor; + // See if there is a palette + Palette *palette = getPalette(stack, client); + const std::string &index = stack.metadata.getString("palette_index", 0); + if (palette && !index.empty()) + return (*palette)[mystoi(index, 0, 255)]; + // Fallback color + return client->idef()->get(stack.name).color; +} + diff --git a/src/client/item_visuals_manager.h b/src/client/item_visuals_manager.h new file mode 100644 index 000000000..bacc4f2b7 --- /dev/null +++ b/src/client/item_visuals_manager.h @@ -0,0 +1,68 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 cx384 + +#pragma once + +#include +#include +#include +#include "wieldmesh.h" // ItemMesh +#include "util/basic_macros.h" + +class Client; +class ItemStack; +typedef std::vector Palette; // copied from src/client/texturesource.h +namespace irr::video { class ITexture; } + +// Caches data needed to draw an itemstack + +struct ItemVisualsManager +{ + ItemVisualsManager() + { + m_main_thread = std::this_thread::get_id(); + } + + void clear() { + m_cached_item_visuals.clear(); + } + + // Get item inventory texture + video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const; + + // Get item wield mesh + // Once said to return nullptr if there is an inventory image, but this is wrong + ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const; + + // Get item palette + Palette* getPalette(const ItemStack &item, Client *client) const; + + // Returns the base color of an item stack: the color of all + // tiles that do not define their own color. + video::SColor getItemstackColor(const ItemStack &stack, Client *client) const; + +private: + struct ItemVisuals + { + video::ITexture *inventory_texture; + ItemMesh wield_mesh; + Palette *palette; + + ItemVisuals(): + inventory_texture(nullptr), + palette(nullptr) + {} + + ~ItemVisuals(); + + DISABLE_CLASS_COPY(ItemVisuals); + }; + + // The id of the thread that is allowed to use irrlicht directly + std::thread::id m_main_thread; + // Cached textures and meshes + mutable std::unordered_map> m_cached_item_visuals; + + ItemVisuals* createItemVisuals(const ItemStack &item, Client *client) const; +}; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index dbba0de2a..11116a5c3 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -23,6 +23,7 @@ #include #include #include +#include "item_visuals_manager.h" #define WIELD_SCALE_FACTOR 30.0f #define WIELD_SCALE_FACTOR_EXTRUDED 40.0f @@ -348,6 +349,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che { ITextureSource *tsrc = client->getTextureSource(); IItemDefManager *idef = client->getItemDefManager(); + ItemVisualsManager *item_visuals = client->getItemVisualsManager(); IShaderSource *shdrsrc = client->getShaderSource(); const NodeDefManager *ndef = client->getNodeDefManager(); const ItemDefinition &def = item.getDefinition(idef); @@ -361,7 +363,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // Color-related m_buffer_info.clear(); - m_base_color = idef->getItemstackColor(item, client); + m_base_color = item_visuals->getItemstackColor(item, client); const std::string wield_image = item.getWieldImage(idef); const std::string wield_overlay = item.getWieldOverlay(idef); diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp index 1afe93395..111109fd4 100644 --- a/src/gui/drawItemStack.cpp +++ b/src/gui/drawItemStack.cpp @@ -13,6 +13,7 @@ #include "client/wieldmesh.h" #include "client/texturesource.h" #include "client/guiscalingfilter.h" +#include "client/item_visuals_manager.h" struct MeshTimeInfo { u64 time; @@ -43,6 +44,7 @@ void drawItemStack( auto *idef = client->idef(); const ItemDefinition &def = item.getDefinition(idef); + ItemVisualsManager* item_visuals = client->getItemVisualsManager(); bool draw_overlay = false; @@ -58,7 +60,7 @@ void drawItemStack( // Render as mesh if animated or no inventory image if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { - imesh = idef->getWieldMesh(item, client); + imesh = item_visuals->getWieldMesh(item, client); has_mesh = imesh && imesh->mesh; } if (has_mesh) { @@ -114,8 +116,7 @@ void drawItemStack( driver->setTransform(video::ETS_WORLD, matrix); driver->setViewPort(viewrect); - video::SColor basecolor = - client->idef()->getItemstackColor(item, client); + video::SColor basecolor = item_visuals->getItemstackColor(item, client); const u32 mc = mesh->getMeshBufferCount(); if (mc > imesh->buffer_info.size()) @@ -154,10 +155,10 @@ void drawItemStack( draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); } else { // Otherwise just draw as 2D - video::ITexture *texture = client->idef()->getInventoryTexture(item, client); + video::ITexture *texture = item_visuals->getInventoryTexture(item, client); video::SColor color; if (texture) { - color = client->idef()->getItemstackColor(item, client); + color = item_visuals->getItemstackColor(item, client); } else { color = video::SColor(255, 255, 255, 255); ITextureSource *tsrc = client->getTextureSource(); diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 23f948f8c..17f46dedc 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -7,14 +7,6 @@ #include "nodedef.h" #include "tool.h" -#include "inventory.h" -#if CHECK_CLIENT_BUILD() -#include "client/mapblock_mesh.h" -#include "client/mesh.h" -#include "client/wieldmesh.h" -#include "client/client.h" -#include "client/texturesource.h" -#endif #include "log.h" #include "settings.h" #include "util/serialize.h" @@ -360,34 +352,10 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) class CItemDefManager: public IWritableItemDefManager { -#if CHECK_CLIENT_BUILD() - struct ClientCached - { - video::ITexture *inventory_texture; - ItemMesh wield_mesh; - Palette *palette; - - ClientCached(): - inventory_texture(NULL), - palette(NULL) - {} - - ~ClientCached() { - if (wield_mesh.mesh) - wield_mesh.mesh->drop(); - } - - DISABLE_CLASS_COPY(ClientCached); - }; -#endif public: CItemDefManager() { - -#if CHECK_CLIENT_BUILD() - m_main_thread = std::this_thread::get_id(); -#endif clear(); } @@ -435,94 +403,6 @@ public: return m_item_definitions.find(name) != m_item_definitions.cend(); } -#if CHECK_CLIENT_BUILD() -protected: - ClientCached* createClientCachedDirect(const ItemStack &item, Client *client) const - { - // This is not thread-safe - sanity_check(std::this_thread::get_id() == m_main_thread); - - const ItemDefinition &def = item.getDefinition(this); - std::string inventory_image = item.getInventoryImage(this); - std::string inventory_overlay = item.getInventoryOverlay(this); - std::string cache_key = def.name; - if (!inventory_image.empty()) - cache_key += "/" + inventory_image; - if (!inventory_overlay.empty()) - cache_key += ":" + inventory_overlay; - - // Skip if already in cache - auto it = m_clientcached.find(cache_key); - if (it != m_clientcached.end()) - return it->second.get(); - - infostream << "Lazily creating item texture and mesh for \"" - << cache_key << "\"" << std::endl; - - ITextureSource *tsrc = client->getTextureSource(); - - // Create new ClientCached - auto cc = std::make_unique(); - - cc->inventory_texture = NULL; - if (!inventory_image.empty()) - cc->inventory_texture = tsrc->getTexture(inventory_image); - getItemMesh(client, item, &(cc->wield_mesh)); - - cc->palette = tsrc->getPalette(def.palette_image); - - // Put in cache - ClientCached *ptr = cc.get(); - m_clientcached[cache_key] = std::move(cc); - return ptr; - } - -public: - // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const ItemStack &item, - Client *client) const - { - ClientCached *cc = createClientCachedDirect(item, client); - if (!cc) - return nullptr; - return cc->inventory_texture; - } - - // Get item wield mesh - virtual ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const - { - ClientCached *cc = createClientCachedDirect(item, client); - if (!cc) - return nullptr; - return &(cc->wield_mesh); - } - - // Get item palette - virtual Palette* getPalette(const ItemStack &item, Client *client) const - { - ClientCached *cc = createClientCachedDirect(item, client); - if (!cc) - return nullptr; - return cc->palette; - } - - virtual video::SColor getItemstackColor(const ItemStack &stack, - Client *client) const - { - // Look for direct color definition - const std::string &colorstring = stack.metadata.getString("color", 0); - video::SColor directcolor; - if (!colorstring.empty() && parseColorString(colorstring, directcolor, true)) - return directcolor; - // See if there is a palette - Palette *palette = getPalette(stack, client); - const std::string &index = stack.metadata.getString("palette_index", 0); - if (palette && !index.empty()) - return (*palette)[mystoi(index, 0, 255)]; - // Fallback color - return get(stack.name).color; - } -#endif void applyTextureOverrides(const std::vector &overrides) { infostream << "ItemDefManager::applyTextureOverrides(): Applying " @@ -666,12 +546,6 @@ private: std::map m_item_definitions; // Aliases StringMap m_aliases; -#if CHECK_CLIENT_BUILD() - // The id of the thread that is allowed to use irrlicht directly - std::thread::id m_main_thread; - // Cached textures and meshes - mutable std::unordered_map> m_clientcached; -#endif }; IWritableItemDefManager* createItemDefManager() diff --git a/src/itemdef.h b/src/itemdef.h index fdad86a69..3feee79d1 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -17,14 +17,7 @@ #include "util/pointabilities.h" #include "util/pointedthing.h" -class IGameDef; -class Client; struct ToolCapabilities; -struct ItemMesh; -struct ItemStack; -typedef std::vector Palette; // copied from src/client/texturesource.h -namespace irr::video { class ITexture; } -using namespace irr; /* Base item definition @@ -142,30 +135,6 @@ public: virtual bool isKnown(const std::string &name) const=0; virtual void serialize(std::ostream &os, u16 protocol_version)=0; - - /* Client-specific methods */ - // TODO: should be moved elsewhere in the future - - // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const - { return nullptr; } - - /** - * Get wield mesh - * @returns nullptr if there is an inventory image - */ - virtual ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const - { return nullptr; } - - // Get item palette - virtual Palette* getPalette(const ItemStack &item, Client *client) const - { return nullptr; } - - // Returns the base color of an item stack: the color of all - // tiles that do not define their own color. - virtual video::SColor getItemstackColor(const ItemStack &stack, - Client *client) const - { return video::SColor(0); } }; class IWritableItemDefManager : public IItemDefManager