mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-09-04 17:23:29 +00:00
minor: yugioh support added
This commit is contained in:
+6
-3
@@ -5,20 +5,23 @@ The `ccm` executable — composition root only. The single place where concrete
|
||||
## File pointers
|
||||
|
||||
- `main.cpp` — the entire app. Defines `CcmApp : public wxApp`, builds the dependency graph in `OnInit()`, then hands an `AppContext` to `MainFrame`.
|
||||
- `CMakeLists.txt` — declares the `ccm` target. Sets `WIN32_EXECUTABLE TRUE` on Windows so no console window appears. Links `ccm_core`, `ccm_ui_wx`, `ccm_warnings`.
|
||||
- `CMakeLists.txt` — declares the `ccm` target. Sets `WIN32_EXECUTABLE TRUE` on Windows so no console window appears. Links `ccm_core`, `ccm_ui_wx`, `ccm_warnings`. **`POST_BUILD`**: creates `$<TARGET_FILE_DIR:ccm>/assets/` and copies `ui_wx/assets/ygo_card_back.png` there so Yu-Gi-Oh! preview fallbacks work offline (see `BaseSelectedCardPanel` / `docs/assets-and-info-apis.md`).
|
||||
|
||||
## Conventions
|
||||
|
||||
1. **Composition root is the only place** that names concrete adapters: `StdFileSystem`, `CprHttpClient`, `JsonCollectionRepository<MagicCard>`, `JsonCollectionRepository<PokemonCard>`, `JsonSetRepository`, `LocalImageStore`, `MagicGameModule`, `PokemonGameModule`, `MagicGameView`, `PokemonGameView`, etc. If a concrete adapter type appears anywhere else in the codebase, move the wiring here.
|
||||
1. **Composition root is the only place** that names concrete adapters: `StdFileSystem`, `CprHttpClient`, `JsonCollectionRepository<MagicCard>`, `JsonCollectionRepository<PokemonCard>`, `JsonCollectionRepository<YuGiOhCard>`, `JsonSetRepository`, `LocalImageStore`, `LocalPreviewByteCache`, `MagicGameModule`, `PokemonGameModule`, `YuGiOhGameModule`, `MagicGameView`, `PokemonGameView`, `YuGiOhGameView`, etc. If a concrete adapter type appears anywhere else in the codebase, move the wiring here.
|
||||
2. **Member declaration order in `CcmApp` matters** — destruction is reverse, so a member that depends on another (e.g. `magicCollSvc_` depends on `magicRepo_` and `imgStore_`; `previewSvc_` depends on `http_` and is consumed by `ctx_`; `magicView_` depends on the typed `magicCollSvc_` and the shared services) must be declared **after** its deps. Do not reorder casually.
|
||||
3. **Use `std::unique_ptr` for everything owned** by `CcmApp`. The `AppContext` then holds plain references into those owned objects, plus a vector of `IGameView*` raw pointers (the `unique_ptr<>`s for the views are the actual owners; the vector just describes the active set).
|
||||
4. **Game-to-directory mapping** lives in `dirNameForGame(Game)` (anonymous namespace). When adding a new game, extend this function — it is wired into all three repositories (`JsonCollectionRepository`, `JsonSetRepository`, `LocalImageStore`).
|
||||
5. **`config.json` location** is the executable's parent directory, resolved via `wxStandardPaths::Get().GetExecutablePath()`. Do not change this — existing installations rely on that location.
|
||||
6. **Image format handlers** must be registered via `wxImage::AddHandler(new wxPNGHandler)` and `new wxJPEGHandler` before any image is loaded. They are added in `OnInit()` first thing — keep it that way.
|
||||
7. **Card preview source ownership** lives inside the `IGameModule`. The composition root never constructs an `<Name>CardPreviewSource` directly; it calls `previewSvc_->registerModule(*<name>Mod_)` and the service pulls the module's preview source via `IGameModule::cardPreviewSource()` (returning `nullptr` is silently skipped).
|
||||
8. **One `CprHttpClient` per app, shared by every consumer.** The single `http_` instance is handed to `SetService`, `CardPreviewService`, and every per-game module. Do **not** construct a second `CprHttpClient` (or pass `cpr::Get(...)` directly) from anywhere — the adapter holds a long-lived `cpr::Session` whose connection cache + TLS keep-alive is what makes repeat lookups fast (game-agnostic; see `core/AGENTS.md` convention 11). The shared instance also gives `CardPreviewService`'s in-memory LRU a single source of truth to cache against.
|
||||
9. **One `LocalPreviewByteCache` per app**, rooted at `<exeDir>/.cache/preview-cache/` — i.e. **next to the executable**, in the same scope as `config.json`. **Do not** root the cache at `config_->current().dataStorage`: the user's data-storage path is user-configurable at runtime and is meant for the user's collection (cards, scans, set lists). Previews are downloaded-from-network artifacts that (a) must not move when the user relocates their collection, (b) must not be uploaded/synced together with the user's data dir, and (c) must not survive a fresh install elsewhere on disk. Pinning the cache to `exeDir` is what gives those properties without writing extra plumbing for each data-storage flow. The umbrella `.cache/` directory is reserved for any future computed-from-network caches (set-list snapshots, etc.); the leading dot keeps it out of the way for users poking around the install folder. Cache updates flow entirely through cache keys: `CardPreviewService` invalidates entries automatically when the cache key changes (record edits) and rewrites them when a same-key resolution flips between positive and negative — there is no `clearCache(...)` API. To wipe the cache manually, delete `<exeDir>/.cache/`; reinstalling / moving the executable also resets the cache by design. Construct the cache after `ConfigService` (so the dependency graph is the same as before; the cache itself only needs `*fs_` and the resolved `exeDir`) and before `CardPreviewService` (so the service can hold a stable raw pointer); declare the member after `config_`/`fs_` and before `previewSvc_` to keep destruction order correct. See `core/AGENTS.md` convention 10 and `docs/caching.md` ("Updating cached entries") for the full cache shape, policy, and update mechanic.
|
||||
|
||||
## Required follow-ups
|
||||
|
||||
- The **`POST_BUILD` copy of `ygo_card_back.png`** must stay in sync with `ui_wx/assets/`; if you relocate install layout or add more bundled assets, mirror the pattern (`make_directory` + `copy_if_different`) and document under `docs/assets-and-info-apis.md` / `ui_wx/AGENTS.md`.
|
||||
- After adding a new game module you **must**: (1) add a `unique_ptr<<Name>GameModule>` member in declaration-order-correct position, (2) construct it in `OnInit()`, (3) call `setSvc_->registerModule(<name>Mod_.get())`, (4) call `previewSvc_->registerModule(*<name>Mod_)` (no-op when the module has no preview source), (5) extend `dirNameForGame`, (6) add a typed `JsonCollectionRepository<<Name>Card>` + `CollectionService<<Name>Card>` if the game has a custom card type, (7) construct a `<Name>GameView` and append its raw pointer to the `AppContext::gameViews` vector, (8) make sure the view's `unique_ptr<>` member sits **after** all its deps (typed services + `IGameModule`).
|
||||
- After adding a new core service you **must** add a `unique_ptr<...>` member, construct it in `OnInit()` after its deps, and add a reference field to `AppContext`.
|
||||
- After adding a new dependency edge you **must** verify destruction order is still correct: deps **before** dependents in the member list.
|
||||
@@ -32,4 +35,4 @@ The `ccm` executable — composition root only. The single place where concrete
|
||||
## Commands
|
||||
|
||||
- Build the binary: `cmake --build build --target ccm`
|
||||
- Run on Windows / MinGW-w64: `.\build\bin\ccm.exe`. The cpr/curl/zlib DLLs are placed next to the exe automatically; the MSYS2 UCRT64 runtime (`libgcc_s_seh-1.dll`, `libstdc++-6.dll`) needs to be on `PATH` (e.g. `P:\msys2\msys64\ucrt64\bin`). On verified runs the exe loads under window title "Card Collection Manager 3".
|
||||
- Run on Windows / MinGW-w64: `.\build\bin\ccm3.exe`. The cpr/curl/zlib DLLs are placed next to the exe automatically; the MSYS2 UCRT64 runtime (`libgcc_s_seh-1.dll`, `libstdc++-6.dll`) needs to be on `PATH` (e.g. `P:\msys2\msys64\ucrt64\bin`). On verified runs the exe loads under window title "Card Collection Manager 3".
|
||||
|
||||
@@ -18,3 +18,10 @@ target_link_libraries(ccm
|
||||
ccm_ui_wx
|
||||
ccm_warnings
|
||||
)
|
||||
|
||||
# Yu-Gi-Oh! preview fallback image (used when network card-back URLs fail).
|
||||
add_custom_command(TARGET ccm POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:ccm>/assets"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${CMAKE_SOURCE_DIR}/ui_wx/assets/ygo_card_back.png"
|
||||
"$<TARGET_FILE_DIR:ccm>/assets/ygo_card_back.png")
|
||||
|
||||
+36
-2
@@ -4,12 +4,15 @@
|
||||
|
||||
#include "ccm/domain/MagicCard.hpp"
|
||||
#include "ccm/domain/PokemonCard.hpp"
|
||||
#include "ccm/domain/YuGiOhCard.hpp"
|
||||
#include "ccm/games/magic/MagicGameModule.hpp"
|
||||
#include "ccm/games/pokemon/PokemonGameModule.hpp"
|
||||
#include "ccm/games/yugioh/YuGiOhGameModule.hpp"
|
||||
#include "ccm/infra/CprHttpClient.hpp"
|
||||
#include "ccm/infra/JsonCollectionRepository.hpp"
|
||||
#include "ccm/infra/JsonSetRepository.hpp"
|
||||
#include "ccm/infra/LocalImageStore.hpp"
|
||||
#include "ccm/infra/LocalPreviewByteCache.hpp"
|
||||
#include "ccm/infra/StdFileSystem.hpp"
|
||||
#include "ccm/services/CardPreviewService.hpp"
|
||||
#include "ccm/services/CollectionService.hpp"
|
||||
@@ -20,6 +23,7 @@
|
||||
#include "ccm/ui/MagicGameView.hpp"
|
||||
#include "ccm/ui/MainFrame.hpp"
|
||||
#include "ccm/ui/PokemonGameView.hpp"
|
||||
#include "ccm/ui/YuGiOhGameView.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/icon.h>
|
||||
@@ -40,6 +44,7 @@ std::string dirNameForGame(ccm::Game g) {
|
||||
switch (g) {
|
||||
case ccm::Game::Magic: return "magic";
|
||||
case ccm::Game::Pokemon: return "pokemon";
|
||||
case ccm::Game::YuGiOh: return "yugioh";
|
||||
}
|
||||
return "magic";
|
||||
}
|
||||
@@ -74,11 +79,14 @@ public:
|
||||
http_ = std::make_unique<ccm::CprHttpClient>();
|
||||
magicMod_ = std::make_unique<ccm::MagicGameModule>(*http_);
|
||||
pokeMod_ = std::make_unique<ccm::PokemonGameModule>(*http_);
|
||||
ygoMod_ = std::make_unique<ccm::YuGiOhGameModule>(*http_);
|
||||
|
||||
magicRepo_ = std::make_unique<ccm::JsonCollectionRepository<ccm::MagicCard>>(
|
||||
*fs_, *config_, &dirNameForGame);
|
||||
pokeRepo_ = std::make_unique<ccm::JsonCollectionRepository<ccm::PokemonCard>>(
|
||||
*fs_, *config_, &dirNameForGame);
|
||||
ygoRepo_ = std::make_unique<ccm::JsonCollectionRepository<ccm::YuGiOhCard>>(
|
||||
*fs_, *config_, &dirNameForGame);
|
||||
setRepo_ = std::make_unique<ccm::JsonSetRepository>(*fs_, *config_, &dirNameForGame);
|
||||
imgStore_ = std::make_unique<ccm::LocalImageStore>(*fs_, *config_, &dirNameForGame);
|
||||
|
||||
@@ -87,19 +95,39 @@ public:
|
||||
*magicRepo_, *imgStore_);
|
||||
pokeCollSvc_ = std::make_unique<ccm::CollectionService<ccm::PokemonCard>>(
|
||||
*pokeRepo_, *imgStore_);
|
||||
ygoCollSvc_ = std::make_unique<ccm::CollectionService<ccm::YuGiOhCard>>(
|
||||
*ygoRepo_, *imgStore_);
|
||||
setSvc_ = std::make_unique<ccm::SetService>(*setRepo_);
|
||||
setSvc_->registerModule(magicMod_.get());
|
||||
setSvc_->registerModule(pokeMod_.get());
|
||||
setSvc_->registerModule(ygoMod_.get());
|
||||
|
||||
previewSvc_ = std::make_unique<ccm::CardPreviewService>(*http_);
|
||||
// Disk-backed preview cache lives next to the executable, in the same
|
||||
// location scope as config.json - NOT inside the user's data-storage
|
||||
// directory. Rationale: previews are downloaded artifacts, not user
|
||||
// data, so they should not move when the user relocates their
|
||||
// collection (data-storage path can be reconfigured at runtime), and
|
||||
// they should not be uploaded together with the user's collection
|
||||
// when the data dir is backed up / synced. The umbrella ".cache/"
|
||||
// directory is reserved for any future computed-from-network caches
|
||||
// (set-list snapshots, etc.); the leading dot keeps it out of the way
|
||||
// for users poking around the install folder. Constructed before
|
||||
// previewSvc_ so the service can hold a stable raw pointer to it.
|
||||
previewCache_ = std::make_unique<ccm::LocalPreviewByteCache>(
|
||||
*fs_,
|
||||
exeDir / ".cache" / "preview-cache");
|
||||
previewSvc_ = std::make_unique<ccm::CardPreviewService>(*http_, previewCache_.get());
|
||||
previewSvc_->registerModule(*magicMod_);
|
||||
previewSvc_->registerModule(*pokeMod_);
|
||||
previewSvc_->registerModule(*ygoMod_);
|
||||
|
||||
// Per-game UI bundles. Order here is the order shown in the Game menu.
|
||||
magicView_ = std::make_unique<ccm::ui::MagicGameView>(
|
||||
*config_, *magicCollSvc_, *setSvc_, *imgSvc_, *previewSvc_, *magicMod_);
|
||||
pokeView_ = std::make_unique<ccm::ui::PokemonGameView>(
|
||||
*config_, *pokeCollSvc_, *setSvc_, *imgSvc_, *previewSvc_, *pokeMod_);
|
||||
ygoView_ = std::make_unique<ccm::ui::YuGiOhGameView>(
|
||||
*config_, *ygoCollSvc_, *setSvc_, *imgSvc_, *previewSvc_, *ygoMod_);
|
||||
|
||||
ctx_ = std::make_unique<ccm::ui::AppContext>(ccm::ui::AppContext{
|
||||
*config_,
|
||||
@@ -108,7 +136,8 @@ public:
|
||||
*previewSvc_,
|
||||
*magicMod_,
|
||||
*pokeMod_,
|
||||
{ magicView_.get(), pokeView_.get() },
|
||||
*ygoMod_,
|
||||
{ magicView_.get(), pokeView_.get(), ygoView_.get() },
|
||||
});
|
||||
|
||||
auto* frame = new ccm::ui::MainFrame(*ctx_);
|
||||
@@ -128,17 +157,22 @@ private:
|
||||
std::unique_ptr<ccm::CprHttpClient> http_;
|
||||
std::unique_ptr<ccm::MagicGameModule> magicMod_;
|
||||
std::unique_ptr<ccm::PokemonGameModule> pokeMod_;
|
||||
std::unique_ptr<ccm::YuGiOhGameModule> ygoMod_;
|
||||
std::unique_ptr<ccm::JsonCollectionRepository<ccm::MagicCard>> magicRepo_;
|
||||
std::unique_ptr<ccm::JsonCollectionRepository<ccm::PokemonCard>> pokeRepo_;
|
||||
std::unique_ptr<ccm::JsonCollectionRepository<ccm::YuGiOhCard>> ygoRepo_;
|
||||
std::unique_ptr<ccm::JsonSetRepository> setRepo_;
|
||||
std::unique_ptr<ccm::LocalImageStore> imgStore_;
|
||||
std::unique_ptr<ccm::ImageService> imgSvc_;
|
||||
std::unique_ptr<ccm::CollectionService<ccm::MagicCard>> magicCollSvc_;
|
||||
std::unique_ptr<ccm::CollectionService<ccm::PokemonCard>> pokeCollSvc_;
|
||||
std::unique_ptr<ccm::CollectionService<ccm::YuGiOhCard>> ygoCollSvc_;
|
||||
std::unique_ptr<ccm::SetService> setSvc_;
|
||||
std::unique_ptr<ccm::LocalPreviewByteCache> previewCache_;
|
||||
std::unique_ptr<ccm::CardPreviewService> previewSvc_;
|
||||
std::unique_ptr<ccm::ui::MagicGameView> magicView_;
|
||||
std::unique_ptr<ccm::ui::PokemonGameView> pokeView_;
|
||||
std::unique_ptr<ccm::ui::YuGiOhGameView> ygoView_;
|
||||
std::unique_ptr<ccm::ui::AppContext> ctx_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user