mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-30 17:38:48 +00:00
major: initial release
* initial development * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * pipeline * ci/cd * ci/cd * ci/cd * ci/cd * ci/cd * ci/cd * ci/cd * pokemon * pokemon * pokemon * pokemon * pokemon * pokemon * improvements * improvements * ci/cd * ci/cd * improvements * improvements * improvements * improvements * improvements * improvements * improvements * improvements * improvements --------- Co-authored-by: sdine <sdine@sdine.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# app/AGENTS.md
|
||||
|
||||
The `ccm` executable — composition root only. The single place where concrete adapter types are mentioned. Read the root `AGENTS.md` first.
|
||||
|
||||
## 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`.
|
||||
|
||||
## 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.
|
||||
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).
|
||||
|
||||
## Required follow-ups
|
||||
|
||||
- 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.
|
||||
- After changing the IGameView contract or the AppContext shape, update `docs/adding-a-new-game.md` so the canonical procedure stays in sync.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- Don't add business logic here. If something is more than `std::make_unique` and a `register/Bind` call, it belongs in `core/`.
|
||||
- Don't construct services on the stack inside `OnInit()` — they must outlive the `MainFrame`, so they live as `CcmApp` members.
|
||||
|
||||
## 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".
|
||||
@@ -0,0 +1,20 @@
|
||||
# ccm: thin executable / composition root.
|
||||
# Wires concrete adapters into the services and hands them to the UI.
|
||||
|
||||
add_executable(ccm
|
||||
main.cpp
|
||||
)
|
||||
set_target_properties(ccm PROPERTIES OUTPUT_NAME ccm3)
|
||||
|
||||
# Subsystem WINDOWS on Windows so we don't get a stray console.
|
||||
if(WIN32)
|
||||
target_sources(ccm PRIVATE ccm.rc)
|
||||
set_target_properties(ccm PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
endif()
|
||||
|
||||
target_link_libraries(ccm
|
||||
PRIVATE
|
||||
ccm_core
|
||||
ccm_ui_wx
|
||||
ccm_warnings
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
ccm_main_icon ICON "resources/ccm.ico"
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// Composition root: builds the dependency graph from the bottom up and hands
|
||||
// it to the wxWidgets UI layer. This is the only place where concrete adapter
|
||||
// types are mentioned - everything downstream depends on interfaces.
|
||||
|
||||
#include "ccm/domain/MagicCard.hpp"
|
||||
#include "ccm/domain/PokemonCard.hpp"
|
||||
#include "ccm/games/magic/MagicGameModule.hpp"
|
||||
#include "ccm/games/pokemon/PokemonGameModule.hpp"
|
||||
#include "ccm/infra/CprHttpClient.hpp"
|
||||
#include "ccm/infra/JsonCollectionRepository.hpp"
|
||||
#include "ccm/infra/JsonSetRepository.hpp"
|
||||
#include "ccm/infra/LocalImageStore.hpp"
|
||||
#include "ccm/infra/StdFileSystem.hpp"
|
||||
#include "ccm/services/CardPreviewService.hpp"
|
||||
#include "ccm/services/CollectionService.hpp"
|
||||
#include "ccm/services/ConfigService.hpp"
|
||||
#include "ccm/services/ImageService.hpp"
|
||||
#include "ccm/services/SetService.hpp"
|
||||
#include "ccm/ui/AppContext.hpp"
|
||||
#include "ccm/ui/MagicGameView.hpp"
|
||||
#include "ccm/ui/MainFrame.hpp"
|
||||
#include "ccm/ui/PokemonGameView.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/icon.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/stdpaths.h>
|
||||
#include <wx/utils.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
// All Game::X -> directory string mappings live in one place. Eliminates the
|
||||
// need for the repositories to know about concrete game module classes.
|
||||
std::string dirNameForGame(ccm::Game g) {
|
||||
switch (g) {
|
||||
case ccm::Game::Magic: return "magic";
|
||||
case ccm::Game::Pokemon: return "pokemon";
|
||||
}
|
||||
return "magic";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CcmApp : public wxApp {
|
||||
public:
|
||||
bool OnInit() override {
|
||||
// wxImage knows about PNG/JPEG once these handlers are registered.
|
||||
wxImage::AddHandler(new wxPNGHandler);
|
||||
wxImage::AddHandler(new wxJPEGHandler);
|
||||
|
||||
// --- locate config next to exe, data in user home ----------------
|
||||
const std::filesystem::path exeDir =
|
||||
std::filesystem::path(wxStandardPaths::Get().GetExecutablePath().ToStdString())
|
||||
.parent_path();
|
||||
const auto configPath = exeDir / "config.json";
|
||||
const auto defaultDataDir =
|
||||
std::filesystem::path(wxGetHomeDir().ToStdString()) / "ccm3-data";
|
||||
|
||||
// --- build the dependency graph -----------------------------------
|
||||
fs_ = std::make_unique<ccm::StdFileSystem>();
|
||||
config_ = std::make_unique<ccm::ConfigService>(*fs_, configPath, defaultDataDir);
|
||||
|
||||
if (auto init = config_->initialize(); !init) {
|
||||
wxMessageBox("Failed to load configuration: " + init.error(),
|
||||
"Startup error", wxOK | wxICON_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
http_ = std::make_unique<ccm::CprHttpClient>();
|
||||
magicMod_ = std::make_unique<ccm::MagicGameModule>(*http_);
|
||||
pokeMod_ = std::make_unique<ccm::PokemonGameModule>(*http_);
|
||||
|
||||
magicRepo_ = std::make_unique<ccm::JsonCollectionRepository<ccm::MagicCard>>(
|
||||
*fs_, *config_, &dirNameForGame);
|
||||
pokeRepo_ = std::make_unique<ccm::JsonCollectionRepository<ccm::PokemonCard>>(
|
||||
*fs_, *config_, &dirNameForGame);
|
||||
setRepo_ = std::make_unique<ccm::JsonSetRepository>(*fs_, *config_, &dirNameForGame);
|
||||
imgStore_ = std::make_unique<ccm::LocalImageStore>(*fs_, *config_, &dirNameForGame);
|
||||
|
||||
imgSvc_ = std::make_unique<ccm::ImageService>(*imgStore_);
|
||||
magicCollSvc_ = std::make_unique<ccm::CollectionService<ccm::MagicCard>>(
|
||||
*magicRepo_, *imgStore_);
|
||||
pokeCollSvc_ = std::make_unique<ccm::CollectionService<ccm::PokemonCard>>(
|
||||
*pokeRepo_, *imgStore_);
|
||||
setSvc_ = std::make_unique<ccm::SetService>(*setRepo_);
|
||||
setSvc_->registerModule(magicMod_.get());
|
||||
setSvc_->registerModule(pokeMod_.get());
|
||||
|
||||
previewSvc_ = std::make_unique<ccm::CardPreviewService>(*http_);
|
||||
previewSvc_->registerModule(*magicMod_);
|
||||
previewSvc_->registerModule(*pokeMod_);
|
||||
|
||||
// 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_);
|
||||
|
||||
ctx_ = std::make_unique<ccm::ui::AppContext>(ccm::ui::AppContext{
|
||||
*config_,
|
||||
*setSvc_,
|
||||
*imgSvc_,
|
||||
*previewSvc_,
|
||||
*magicMod_,
|
||||
*pokeMod_,
|
||||
{ magicView_.get(), pokeView_.get() },
|
||||
});
|
||||
|
||||
auto* frame = new ccm::ui::MainFrame(*ctx_);
|
||||
#ifdef __WXMSW__
|
||||
frame->SetIcon(wxICON(ccm_main_icon));
|
||||
#endif
|
||||
frame->Show(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Order matters - destruction is reverse, so put services that depend on
|
||||
// others *after* their deps in the member list. Game views are torn down
|
||||
// first so their panels release any references to the typed services.
|
||||
std::unique_ptr<ccm::StdFileSystem> fs_;
|
||||
std::unique_ptr<ccm::ConfigService> config_;
|
||||
std::unique_ptr<ccm::CprHttpClient> http_;
|
||||
std::unique_ptr<ccm::MagicGameModule> magicMod_;
|
||||
std::unique_ptr<ccm::PokemonGameModule> pokeMod_;
|
||||
std::unique_ptr<ccm::JsonCollectionRepository<ccm::MagicCard>> magicRepo_;
|
||||
std::unique_ptr<ccm::JsonCollectionRepository<ccm::PokemonCard>> pokeRepo_;
|
||||
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::SetService> setSvc_;
|
||||
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::AppContext> ctx_;
|
||||
};
|
||||
|
||||
wxIMPLEMENT_APP(CcmApp);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Reference in New Issue
Block a user