State Management (The Store)
Relevant source files
The following files were used as context for generating this wiki page:The Store is the central component responsible for handling the state and lifecycle of all game data within the SOFL application. It acts as a container for game objects, manages their addition and removal, and orchestrates processing tasks through a system of modular Manager classes and Pipelines. The entire system is initialized at application startup, where the Store instance is created and populated with various managers that handle specific tasks like file operations, UI updates, and metadata fetching.
The architecture is designed to be extensible, allowing new functionality to be added by creating new Manager classes without modifying the core Store logic. When a game is added, it is passed through a Pipeline that executes a series of managers to process it, ensuring that all necessary data is loaded, saved, and displayed correctly.
Sources: sofl/store/store.py, sofl/main.py:80, 150-160
Core Architecture
The state management system is composed of three main components: the Store, Managers, and Pipelines. They work together to manage the collection of Game objects.
The following diagram illustrates the relationship between these core classes.
The Store Class
The Store class is the heart of the state management system. It is instantiated as a singleton accessible via shared.store during the application's lifecycle. Its primary role is to be the single source of truth for all game data.
Sources: sofl/store/store.py:23-34, sofl/main.py:80
Key Attributes
| Attribute | Type | Description |
|---|---|---|
managers | dict[type[Manager], Manager] | A dictionary holding all registered manager instances, keyed by their class type. |
pipeline_managers | set[Manager] | A subset of managers that are executed when a new game is added via a Pipeline. |
pipelines | dict[str, Pipeline] | A dictionary of active pipelines, keyed by game_id. |
source_games | MutableMapping[str, MutableMapping[str, Game]] | The main data structure holding all Game objects, grouped by their base source (e.g., 'steam', 'lutris', 'online-fix'). |
new_game_ids | set[str] | A set to track the IDs of newly added games during an import or add operation. |
duplicate_game_ids | set[str] | A set to track game IDs that were ignored because they were duplicates. |
Sources: sofl/store/store.py:26-33
Key Methods
add_manager(manager: Manager, in_pipeline: bool = True): Registers a new manager with the store. Thein_pipelineflag determines if the manager should be part of the default processing pipeline for new games.add_game(game: Game, additional_data: dict, run_pipeline: bool = True): The main entry point for adding a game to the store. It checks for duplicates, adds the game to thesource_gamescollection, connects manager signals, and optionally initiates aPipelineto process the game.cleanup_game(game: Game): Removes a game's associated files (e.g., JSON data, cover art) from the disk.__getitem__(game_id: str): Allows retrieving a game object from the store using dictionary-style access (e.g.,store["game_id"]).
Sources: sofl/store/store.py:73-80, 121-163
The Manager System
Managers are modular classes that perform specific actions on game objects. All managers inherit from the abstract base class Manager. This design allows for a clean separation of concerns, where each manager is responsible for a single aspect of game processing, such as saving data to a file, updating the UI, or fetching cover art.
Sources: sofl/store/managers/manager.py
Base Manager Class
The base Manager class defines the interface for all managers.
| Attribute/Method | Description |
|---|---|
signals: set[str] | A set of GObject signal names that this manager listens to. When a Game object emits one of these signals, the manager's run method is triggered. |
run(self, game: Game, additional_data: dict) | The core method that contains the manager's logic. It is executed either by a signal or by a Pipeline. |
Sources: sofl/store/managers/manager.py:20-25
Concrete Manager Examples
FileManager: Responsible for saving the game's data to a.jsonfile on disk. It runs within the pipeline.DisplayManager: Responsible for adding the game's widget to the UI (the game list). It also runs within the pipeline.CoverManager,SteamAPIManager,SgdbManager: These managers handle fetching and processing game metadata and cover art. They are added to the store after the initial disk load and are part of the import pipeline.
Sources: sofl/store/managers/file_manager.py, sofl/store/managers/display_manager.py, sofl/main.py:156-159
The Pipeline System
A Pipeline is a mechanism for processing a newly added game by executing a sequence of managers on it. When store.add_game() is called with run_pipeline=True, a new Pipeline instance is created for that game.
The pipeline holds a reference to the Game object and a set of Managers to execute. The advance() method iterates through the managers and calls their run() method, effectively passing the game from one processing stage to the next. This ensures that a new game is saved, displayed, and has its metadata fetched in a controlled sequence.
Sources: sofl/store/pipeline.py, sofl/store/store.py:159-162
Data Flow
The data flow for adding a game is a critical process orchestrated by the Store.
Adding a Game
The sequence diagram below shows the process when store.add_game() is called.
This diagram shows the add_game flow. The Store first validates the game. If it's a new game, it's added to the internal source_games mapping, signals are connected, and a Pipeline is created and started to process it through the registered managers.
Sources: sofl/store/store.py:121-163, sofl/store/pipeline.py:21-27
Application Initialization
The Store and its Managers are set up at the very beginning of the application's lifecycle in sofl/main.py.
This flow shows that managers are added in a specific order. FileManager is added first to allow loading existing games from disk. DisplayManager and other network-related managers are added afterward to process newly imported games.
Sources: sofl/main.py:150-160
Conclusion
The Store provides a robust and modular architecture for managing game state in SOFL. By separating concerns into the Store (container), Manager (logic), and Pipeline (process flow), the system remains easy to maintain and extend. This design allows developers to add new features, such as support for new metadata sources or different storage backends, by simply creating new Manager classes and registering them with the Store, with minimal changes to the existing codebase.