-
Notifications
You must be signed in to change notification settings - Fork 92
Design ‐ New Automation System
WORK IN PROGRESS
This document will describe the architecture for upcoming updates that will improve on the current implementation of automation for the game.
The current implementation of automation uses a dedicated entry point for the game in the file src/auto.py. This entry point is called with parameters indicating the difficulty level or sandbox config to use along with the filename of an automation script. The script is compiled using Python's built-in compile function, and passed to the Stage object that is created based on the provided difficulty level or sandbox config. The Stage object also has a standalone property that is set to True. The game is then launched on that stage directly, and the GameManager instance is instructed to ignore events (inputs).
The stage's setup method calls a private method named _prepare_automation_script. This method defines globals that the script will be able to access (num_cpus, num_ram_pages, num_swap_pages, cpu_core_types), then runs the compiled script using python's built-in exec function. At that point, the script sets a global named scheduler, that the stage retrieves and assigns to its _script_callback attribute. The scheduler global is an instance of a subclass of the Scheduler class defined in automation/api.py.
Each frame, the stage and stage's scene objects notify the game_monitor module of all important events happening in the game by calling functions such as notify_page_swap, notify_process_starvation, etc. The stage's _process_script_events private method is then called. That method executes self._script_callback(game_monitor.get_events()), which calls the scheduler's __call__ method defined in automation/api.py. The method receives all game events that were registered since last frame. It updates the scheduler's internal attributes, which are basically a mirror of the game's state. After that, the scheduler's schedule method, which contains the actual automation logic, is called. At the end, the scheduler returns a list of actions (ex: move a process to CPU), which are finally retrieved by the stage's _process_script_events and executed.
- Implement a CLI that will allow an AI agent to play the game
- Remove the need for the client to maintain its own representation of the game's state
- Limit impacts on the automation code when implementing new features in the game (new game features should require minimal changes to the automation system)
- Make the automation system suitable for E2E tests
- Expose a direct API to control the game without using a script or a CLI.
- The API implementation should be decoupled from the transport layer.
- At least TCP and Websocket should be supported for transport, and it should be possible to implement other transport protocols in the future.
- Make the API expose a text representation of the game interface and allow emulation of mouse and keyboard inputs as a more "low-level" mode of interaction with the game. This would be complementary to the "high-level" mode of the API exposing logical state and allowing to run logical actions.
- Allow control of the whole game from the main menu, not just the stage.
- Make automation also possible when running the game in a web browser.
- Stage code is tightly coupled to the automation system, which complicates maintenance as it increases the risk of breaking automation while making changes to the stage.
- Automated actions are executed synchronously as part of the game loop, which would complicate the implementation of a CLI.
- Automation script has to maintain its own representation of the game's state. This adds complexity and possibilities of bugs. It also makes automation-based E2E testing less reliable as it would test the automation client as much as the game itself.
- Automated actions and player actions sometimes take different paths in the code, which also complicates the use of automation for E2E testing.
- Only the stage can be interacted with, so complete E2E testing through the automation system is not supported.
This phase will implement small changes that will make it easier to implement the new automation API later. The current scripting system will continue to work as is after this phase.
The codebase currently uses both the terms actions and events to refer to mouse and keyboard inputs. This creates ambiguities as the words action and event are also used for in-game concepts (ex: I/O events, I/O queue action, process state events, etc). Furthermore, the word event is used to refer both to events sent to game_monitor for the purpose of notifying the automation script about them, and to events sent by the automation script to perform actions in the game.
The new architecture will clearly differentiate between these concepts:
-
Inputwill be the exclusive term used to refer to mouse and keyboard inputs. This involves changes to the engine code:-
GameEventwill becomeInput -
GameEventTypewill becomeInputType - Any use of the word
eventoreventsin the codebase to refer to player inputs will be changed toinputorinputs - The
updatemethod ofScenewill now have aninputsargument instead ofevents. Any subclass that currently renames this parameter toplayer_actionswill change it toinputsinstead.
-
-
Actionwill refer to a logical action that can be performed by the player (ex: assign a process to a CPU, swap a page, etc). In the final architecture, there will be no distinction between human player actions and automated actions. -
Eventwill refer either to an in-game event not necessarily triggered by the player (ex: I/O event, process termination, etc), or to internal state machine events (see Process State Machine). The termeventwill need to always be prefixed by a context-appropriate term (ex:io_event,state_event). -
Notificationwill replaceEventto refer to information currently sent togame_monitor.
- A new abstract class called
GameObjectwill be introduced. BothSceneandSceneObjectwill inherit from that class.-
Implemented by #222
-
-
Scene's_scene_objectsandSceneObject's_childrenwill be unified intoGameObject's_childrenprivate attribute andchildrenpublic property.
- The words
eventcurrently used in the game monitor will becomenotification, both for the module's public and private members.
-
_process_script_eventsand_get_script_eventswill become_process_script_actionsand_call_script_scheduler.Notificationswill be sent to the script instead ofevents. Vocabulary used in the methods' code will be updated accordingly.
No change will be made to the vocabulary currently used in automation/api.py, the word "event" can stay in use script-side as this does not impact the game's codebase.
This phase implements the new Action concept explicitly, and makes the changes that are necessary to solve the problem of player actions and automated actions following two different paths.
- As defined in phase 1,
actionnow refers to any logical action that can be performed by the player (ex: assign a process to a CPU, swap a page, etc), whileinputdescribes a raw mouse or keyboard input. - This phase will refactor the game's architecture to implement the concept of actions more explicitly. In the new architecture, an
inputwith an effect on an object will generate anactioninstead of being processed directly. - The following members will be added to
GameObject:- A
run_action(type)abstract method - An
action_typesabstract class property that returns anenumof action types that are available for this object. Theenumis specific to the subclass. Enum values should be strings that are identical to the enum names.
- A
- These changes are made to
GameObjectinstead ofSceneObjects, as a scene can also define actions (ex: "show the in-game menu"). - All scenes and scene objects will have to implement the
run_actionmethod and theaction_typesproperty. - The
updatemethod of all scenes and scene objects will need to be changed to callrun_actionfor everyinputthat should trigger an action. - The
_process_script_actionsmethod of classStageneeds to be updated to callrun_actioninstead of processing actions directly.
This phase will implement a unified interface to access game objects' public state. The same interface will be used both by the game's UI and the future API.
- The following attributes will be added to
GameObject:- An abstract
state_dictproperty that returns a dictionary describing the current public state of the object - An abstract
attr_dictproperty that returns a dictionary containing public attributes of the object- The difference between
state_dictandattr_dictis thatstate_dictchanges throughout the object's life-cycle whileattr_dictis static (ex: a process'PIDis part of itsattr_dict, not itsstate_dict).attr_dictprovides "logical" identification for the object other than theobject_id.
- The difference between
- An abstract
-
SceneObjectalready has aview_varsproperty that exposes a dictionary containing extra information needed by the object's view. The current implementation for this property will remain as is. - The
Drawableclass will be renamed toViewas it has no other use than defining a scene object's view. - The following changes will be made to views:
- All views will be refactored to not hold a reference to the corresponding scene object anymore. They will instead receive the
state_dict,attr_dictview_varsdictionaries. In the future, this change will organically ensure that all information that is exposed to a human player will also be exposed to the API. - The constructor will receive an
attr_dictparameter - The
drawmethod will receivestate_dictandview_vars
- All views will be refactored to not hold a reference to the corresponding scene object anymore. They will instead receive the
- All scene objects will need to be updated to accommodate the changes described above.
- In some cases, this will involve more important changes that will require more thorough design on a per-object basis. For example,
ProcessViewcurrently accessesself._process.cpu.process_happiness_ms, which will need to be addressed in the new architecture.
- In some cases, this will involve more important changes that will require more thorough design on a per-object basis. For example,
- In the case of
Scene, itsstate_dictandattr_dictdictionaries will probably be empty for now, but will be useful in the future to communicate scene metadata to the API (ex: current difficulty level).
-
GameObjectneeds a newobject_idproperty that returns a unique identifier for the object. The value returned could probably be that ofid(self), but the actual implementation ofobject_idis irrelevant and the API should make no guarantee on the format or even the return type ofobject_id. - Add an abstract
serializemethod toGameObject. This method returns a dictionary. -
Scene's implementation ofserializeshould return a dictionary in the following format:
{
"object_id": <object_id>,
"object_type": <classname>,
"attrs": <attrs_dict>,
"state": <state_dict>,
"available_actions": [<action_type_string>, ...]
"children": [<serialized_scene_object>, ...],
}-
SceneObject's implementation ofserializeshould return a dictionary in the same format, but with aview_varskey added. -
GameManagerneeds to expose aget_serialized_game_statemethod. This method should return a dictionary in this format:
{
"current_scene": <serialized_scene>
}This is currently unstructured and incomplete. Architecture changes that are not needed for previous phases were moved here.
-
GameObjectwill also have:- A
propagate_action(target, type)method.targetis an object ID andtypeis a value fromaction_types. The method either callsrun_actionif the target isself, orpropagate_actionto each object inchildrenif the target is another object. If the target is notselfand the object has no child, the action is dropped. This allows to recursively propagate the action in the object tree until it reaches its target.
- A