Skip to content
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

Merged
merged 17 commits into from
Aug 21, 2023
Merged

Implement automation mechanism #78

merged 17 commits into from
Aug 21, 2023

Conversation

Wiguwbe
Copy link
Contributor

@Wiguwbe Wiguwbe commented Aug 18, 2023

(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:

  • Process, Page and IoQueue have static variables to keep a track of created instances (1)
  • Page is identified by its pair PID (owner ID) and IDX (index of page inside process)
  • 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.

(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)

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.
@@ -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):
Copy link
Contributor Author

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

Copy link
Owner

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.

Copy link
Contributor Author

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
@plbrault plbrault linked an issue Aug 18, 2023 that may be closed by this pull request
class PageManager(GameObject):
_TOTAL_ROWS = 11
_NUM_RAM_COLS = 16
Copy link
Contributor Author

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

Copy link
Owner

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
Copy link
Contributor Author

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 :)

Copy link
Owner

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():
Copy link
Contributor Author

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

# for automation
self._script_callback = None
if self._script:
# pylint: disable=exec-used
Copy link
Contributor Author

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

Copy link
Owner

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?

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
Copy link
Contributor Author

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)

@Wiguwbe
Copy link
Contributor Author

Wiguwbe commented Aug 19, 2023

Merged from upstream and pylint should give a 10/10 now.
Some warning were disabled and explained above

Copy link
Owner

@plbrault plbrault left a 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:**

Copy link
Owner

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)

Copy link
Contributor Author

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 :)



#
# the main entrypoint to run the scheduler
Copy link
Owner

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

Copy link
Contributor Author

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:]
Copy link
Owner

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

Copy link
Contributor Author

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 :)

Copy link
Contributor Author

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.

Copy link
Owner

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.

Copy link
Contributor Author

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 #...

Copy link
Owner

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?

@@ -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):
Copy link
Owner

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.

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):
Copy link
Owner

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.


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):
Copy link
Owner

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.

@@ -0,0 +1,110 @@
# event manager to gather events from game objects and
Copy link
Owner

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

# for automation
self._script_callback = None
if self._script:
# pylint: disable=exec-used
Copy link
Owner

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?

@@ -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():
Copy link
Owner

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

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
Copy link
Owner

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?

Copy link
Contributor Author

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 dicts

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.
@Wiguwbe
Copy link
Contributor Author

Wiguwbe commented Aug 19, 2023

Latest commit should address most of the suggestions, those it doesn't, they are explained :)

Regarding the request for changes, keep them coming :)

# 'PAGE_NEW',
# 'PAGE_USE',
# 'PAGE_SWAP',
# 'PAGE_FREE',
Copy link
Contributor

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
@Wiguwbe
Copy link
Contributor Author

Wiguwbe commented Aug 19, 2023

The example was now renamed to skeleton and now provides a more OO approach

plbrault and others added 2 commits August 19, 2023 21:07
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
Copy link
Contributor Author

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
Copy link
Contributor Author

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)

@Wiguwbe
Copy link
Contributor Author

Wiguwbe commented Aug 20, 2023

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:]
Copy link
Owner

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?

@plbrault
Copy link
Owner

Looks all good to me! Is this ready to be merged?

@Wiguwbe
Copy link
Contributor Author

Wiguwbe commented Aug 21, 2023

let me just add the credit :)

@Wiguwbe
Copy link
Contributor Author

Wiguwbe commented Aug 21, 2023

Whenever you're ready @plbrault

@plbrault plbrault merged commit 8174b7b into plbrault:main Aug 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature Request] Autoplay
3 participants