-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement automation mechanism #78
Conversation
An external script can be used to automate the scheduling. An event manager receives events from processes, pages and IO queue, and dispatches them to the external script. The external script then processes the events and returns a list of actionable events (simulating clicks). The game scene dispatches the received events to the respective receivers. Main changes are: - event manager; - changes to provide events to event manager; - compile and execute an external script; - _on_click/_onClick is now public; - game's scene dispatches events from script before calling the update function An example automated script is provided, mostly to explain the exposed API, it has a (not so) simple example implementation.
src/game_objects/process.py
Outdated
@@ -62,13 +65,34 @@ def starvation_level(self): | |||
def display_blink_color(self): | |||
return self._display_blink_color | |||
|
|||
def _update_blocking_condition(self, update_fn): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was this section recently removed? it may have been re-enabled in my rebase mess
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was not removed, but it was moved lower. So it appears to be duplicated now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this duplicate was removed
it was previously changed to accomodate events coming from the automation script. as the way those events were handled changed, this change was left behind
src/game_objects/page_manager.py
Outdated
class PageManager(GameObject): | ||
_TOTAL_ROWS = 11 | ||
_NUM_RAM_COLS = 16 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added class constant (and methods below) to be used from scene.Game to pass to the automated script
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename it to _NUM_PAGE_COLS
or event just _NUM_COLS
(which would be consistent with _TOTAL_ROWS
) to make it clear that it's also for the rows of pages that are on disk?
if self.has_cpu: | ||
self.yield_cpu() | ||
else: | ||
self.use_cpu() | ||
|
||
def update(self, current_time, events): | ||
# pylint: disable=too-many-statements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have a too-many-statements warning (51/50)
a refactor could/should take place, but I'm not confident enough to do it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might disable this project-wide actually, I don't need pylint to be THAT judgemental!
@@ -13,6 +13,23 @@ | |||
from game_info import TITLE | |||
from window_size import WINDOW_SIZE | |||
|
|||
|
|||
def compile_auto_script(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
transformed into a function so that pylint doesn't complain about constants without uppercase
src/scenes/game.py
Outdated
# for automation | ||
self._script_callback = None | ||
if self._script: | ||
# pylint: disable=exec-used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pylint complains about exec
being used, which in this implementation is necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you move lines 77 to 94 to another method?
src/scenes/game.py
Outdated
Process.Processes[event['pid']].on_click() | ||
elif event['type'] == 'page': | ||
Page.Pages[(event['pid'],event['idx'])].on_click() | ||
except Exception as exc: # pylint: disable=broad-exception-caught |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this broad exception catches any problem that may come from each event
(I had a few of indexError and valueError when testing my own script)
Merged from upstream and pylint should give a 10/10 now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great overall! I hope I am not being too picky with my change requests. And sorry for causing you more work by introducing pylint in the meantime!
@@ -36,6 +36,14 @@ pipenv run desktop | |||
pipenv run web | |||
``` | |||
|
|||
**Run desktop version with an automated script:** | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you feel about crediting yourself here? (Like "Implemented by @Wiguwbe" or something)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm okay with that, let us solve these problems first :)
automated_example.py
Outdated
|
||
|
||
# | ||
# the main entrypoint to run the scheduler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if this should be a docstring instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was actually thinking in changing the example to have it object oriented and make it a skeleton instead.
run-desktop.py
Outdated
@@ -3,5 +3,6 @@ | |||
|
|||
subprocess.run([ | |||
'python', | |||
'main.py' | |||
'main.py', | |||
*sys.argv[1:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe assign this to a variable with a self-explanatory name first, to make it clearer what this argument is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could also make sense to have a custom entrypoint script for the automated run.
This would just display the Game scene and wouldn't even need to handle (much of) pygame/user input.
It would also have the difficulty configuration from CLI.
I'll leave that for last 😄 , in the meantime I'll do as suggested :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking back, this was a quick work-around to pass the extra arguments coming from pipenv run
into the script.
As far as I'm aware, we can't assign the first argument to a variable because it may not even exist. And I don't know how sys.args (or bash/exec) handles python's None
in arguments (which would be the default).
An option would be to create 2 explanatory lists (with and without file argument), but I think I would prefer to go with a dedicated script/entrypoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A dedicated entry point would make sense, maby something like auto.py
instead of main. And it could take a difficulty level or custom config as an argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the idea, to configure the difficulty, even allowing to customize the difficulty from the command line, something like:
# set base difficulty "easy" but set num_cpus to 6
pipenv run auto --easy --num-cpus 6 #...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Wiguwbe should this argument be removed now?
src/game_objects/io_queue.py
Outdated
@@ -32,13 +38,13 @@ def _check_if_clicked_on(self, event): | |||
return self._view.collides(*event.get_property('position')) | |||
return False | |||
|
|||
def _on_click(self): | |||
def on_click(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer to make the process_events
method public instead of on_click
.
src/game_objects/page.py
Outdated
self._in_use = value | ||
|
||
def _check_if_clicked_on(self, event): | ||
if event.type in [GameEventType.MOUSE_LEFT_CLICK, GameEventType.MOUSE_LEFT_DRAG]: | ||
return self._view.collides(*event.get_property('position')) | ||
return False | ||
|
||
def _on_click(self): | ||
def on_click(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I would do like in IoQueue with the process_events
method: wrap the line self._page_manager.swap_page(self)
into a swap
method and call it in _on_click
. Then this one could be public instead of _on_click
. I would still keep the _on_click
method even if it might seem superfluous, as I like having a very clear entrypoint for when a click happens.
src/game_objects/process.py
Outdated
|
||
def _check_if_clicked_on(self, event): | ||
if event.type == GameEventType.MOUSE_LEFT_CLICK: | ||
return self.starvation_level < 6 and self._view.collides( | ||
*event.get_property('position')) | ||
return False | ||
|
||
def _on_click(self): | ||
def on_click(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, would create an intermediary method and make that one public instead. Maybe toggle
, but I'm open to better ideas.
src/lib/event_manager.py
Outdated
@@ -0,0 +1,110 @@ | |||
# event manager to gather events from game objects and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be a docstring
src/scenes/game.py
Outdated
# for automation | ||
self._script_callback = None | ||
if self._script: | ||
# pylint: disable=exec-used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you move lines 77 to 94 to another method?
src/scenes/game.py
Outdated
@@ -137,5 +173,17 @@ def update(self, current_time, events): | |||
if dialog is not None: | |||
dialog.update(current_time, events) | |||
else: | |||
# first, the script generated events | |||
for event in self._get_script_events(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would also move this block to another method
src/game_objects/io_queue.py
Outdated
from lib.game_object import GameObject | ||
from lib.game_event_type import GameEventType | ||
from game_objects.views.io_queue_view import IoQueueView | ||
|
||
|
||
class IoQueue(GameObject): | ||
|
||
Instance = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not fond of Instances
, Pages
and Processes
being directly in the classes. Could these be held elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense, I believe both ProcessManager and PageManager can hold these dict
s
the "on_click" methods return to private, the underlying calls are now extracted into their own methods and made public; PageManager's `_NUM_RAM_COLS` renamed to `_NUM_COLS`; module's docstring added to event_manager; blocks of code inside `scenes/game.py` related to automation are extracted to their own methods; class instances/maps (`IoQueue.Instance`, `Page.Pages`, `Process.Processes`) were made instance variables. PageManager and ProcessManager now function also (slightly) as dictionaries/maps of the pages/processes.
Latest commit should address most of the suggestions, those it doesn't, they are explained :) Regarding the request for changes, keep them coming :) |
automated_example.py
Outdated
# 'PAGE_NEW', | ||
# 'PAGE_USE', | ||
# 'PAGE_SWAP', | ||
# 'PAGE_FREE', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation
The event_manager was also changed to pass the event type as a string (more useful for the current implementation of the skeleton) With that, there is no need to pass the event types "enum" to the automation script
The example was now renamed to skeleton and now provides a more OO approach |
It is used to run the game with an automated script. The script parses the filename and difficulty configuration from the command line. Only events from the script are processed (no clicks at all) The game scene now has a standalone mode option, which removes all buttons. The game over dialog doesn't have buttons either, in standalone mode. The script only launches the standalone Game scene. A small fix on the automated skeleton was also provided.
@@ -12,10 +14,14 @@ | |||
|
|||
|
|||
class Game(Scene): | |||
def __init__(self, screen, scenes, config=None): | |||
# pylint: disable=too-many-arguments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to support the extra standalone
argument, the too-many-arguments
error would appear (6/5)
@@ -4,28 +4,31 @@ | |||
|
|||
|
|||
class GameOverDialog(GameObject): | |||
|
|||
def __init__(self, uptime, score, restart_game_fn, main_menu_fn): | |||
# pylint: disable=too-many-arguments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to support the extra standalone
argument, the too-many-arguments
error would appear (6/5)
(same as scene/game.py)
I made the auto entrypoint and modified the Game scene to be able to run "standalone" |
run-desktop.py
Outdated
@@ -3,5 +3,6 @@ | |||
|
|||
subprocess.run([ | |||
'python', | |||
'main.py' | |||
'main.py', | |||
*sys.argv[1:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Wiguwbe should this argument be removed now?
Looks all good to me! Is this ready to be merged? |
let me just add the credit :) |
Whenever you're ready @plbrault |
(a bit of a fuss with rebase on #75 and got automatically closed, re-creating)
An external script can be used to automate the scheduling.
An event manager receives events from processes, pages and IO queue,
and dispatches them to the external script.
The external script then processes the events and returns a list of
actionable events (simulating clicks).
The game scene dispatches the received events to the respective receivers.
Main changes are:
An example automated script is provided, mostly to explain the
exposed API, it has a (not so) simple example implementation.
(1). The Process/Page Managers only have lists of them, this needed (in my opinion) a mapping (int -> object), this also allows for them to be fetch from anywhere (maybe not a lot safe). (There are safer options, I may look into them in the future)