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
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ python_version = "3.11"

[scripts]
desktop = "python ./run-desktop.py"
auto = "python ./run-auto.py"
web = "python ./run-web.py"
pylint = "pylint ./src"
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 :)

```bash
pipenv run desktop <script.py>
```

See `automated_example.py` for more info on API.

**Build web version without running:**

```bash
Expand Down
251 changes: 251 additions & 0 deletions automated_skeleton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
"""Skeleton for automation script


globals passed are:
`num_cpus` number of available CPUs
`num_ram_pages` number of pages that fit on RAM
`num_swap_pages` number of pages that fit on SWAP


the event type passed are one of the following:
'IO_QUEUE'
'PAGE_NEW'
'PAGE_USE'
'PAGE_SWAP'
'PAGE_FREE'
'PROC_NEW'
'PROC_CPU'
'PROC_STARV'
'PROC_WAIT_IO'
'PROC_WAIT_PAGE'
'PROC_TERM'
'PROC_KILL'

a process is identified by its PID

a memory page are identified by the owner's PID
and an index IDX inside the process
(first page is idx=0, second page is idx=1, etc...)

the game expects a callable `run_os`, that takes as argument
the list of events generated by the game objects (processes, pages, etc...)
and expects another list of action events to be returned

see `src/lib/event_manager.py` for more info on events generated
"""



class RunOs:
"""Object oriented skeleton for automation script

this implements a `__call__` method that should be exposed
to the game. This method then routes the events to the handlers.

The handlers should be in the form of `handle_<EVENT_TYPE>`
(it's similar to http.server.BaseHTTPRequestHandler).

The helper functions `move_*` and `do_io` will append events
to the list that shall be sent back to the game.
"""

# recommended to keep track of processes
procs = []
in_cpu = []
wait_io = []
wait_page = []

# recommended to keep track of pages
ram_pages = []
swap_pages = []

_event_queue = []

def move_page(self, pid, idx):
"""create a move page event"""
self._event_queue.append({
'type': 'page',
'pid': pid,
'idx': idx
})

def move_process(self, pid):
"""create a move process event"""
self._event_queue.append({
'type': 'process',
'pid': pid
})

def do_io(self):
"""create a process io event"""
self._event_queue.append({
'type': 'io_queue'
})

def __call__(self, events: list):
"""Entrypoint from game

will dispatch each event to the respective handler,
collecting action events to send back to the game,
if a handler doesn't exist, will ignore that event.
"""
self._event_queue.clear()
for event in events:
handler = getattr(self, f"handle_{event.etype}", None)
if handler is not None:
handler(event)
return self._event_queue

#
# implement those below
#

def handle_IO_QUEUE(self, event):
"""IO Queue has new count

triggered when the IO count in the IO queue has changed

event:
.io_count: number of IO waiting to be dispatched
"""

def handle_PAGE_NEW(self, event):
"""A new memory page was created

triggered when a process creats a new page, may be in swap

event:
.pid: id of the owner process
.idx: index of page in process
.swap: bool, if page is in swap
.use: bool, if page is in use
"""

def handle_PAGE_USE(self, event):
"""A page 'use' flag has changed

triggered when either a page was not is use and is now
in use
or the page was in use and is now _not_ in use

this usually comes from a process being moved into or out of
the CPU

event:
.pid: id of the owner process
.idx: index of page in process
.use: bool, if page is in use
"""

def handle_PAGE_SWAP(self, event):
"""A page was swapped

this happens mostly as a response from a swap request

event:
.pid: id of the owner process
.idx: index of page in process
.swap: bool, where it is now
"""

def handle_PAGE_FREE(self, event):
"""A page is freed

this is triggered when a process is terminated

event:
.pid: id of the owner process
.idx: index of page in process
"""

def handle_PROC_NEW(self, event):
"""A new process is created

this happens mostly as the game goes on,
the initial starvation level is 1 (it starts at 0)

event:
.pid: id of the process
"""

def handle_PROC_CPU(self, event):
"""A process was moved into or out of a CPU

this happens mostly as a response from a process
move action

event:
.pid: id of the process
.cpu: bool, if is in CPU or not
"""

def handle_PROC_STARV(self, event):
"""A process' starvation level has changed

this is either increasing because it doesn't have
processing time,
or the process was on the CPU

event:
.pid: id of the process
.starvation_level: the new starvation level
"""

def handle_PROC_WAIT_IO(self, event):
"""A process wait (IO) status has changed

this happens either randomly (blocking)
or an IO event has been processed

event:
.pid: id of the process
.waiting_for_io: bool, the new waiting status
"""

def handle_PROC_WAIT_PAGE(self, event):
"""A process wait (for PAGE) status has changed

this happens either because the process was scheduled
and a memory page is in SWAP (a page can be created into SWAP)
or it is not longer waiting

event:
.pid: id of the process
.waiting_for_page: bool, the new waiting status
"""

def handle_PROC_TERM(self, event):
"""A process was succesfully terminated

this happens randomly when a process is in the CPU,

after being moved from the CPU, the process will disappear

event:
.pid: id of the process
"""

def handle_PROC_KILL(self, event):
"""A process was killed by the user

this happens if the starvation level is too high (level 5, 0 based)

the process disappeared from the process list

event:
.pid: id of the process
"""


#
# the main entrypoint to run the scheduler
#
# it expects a callable `run_os`
#
# it receives a list of events generated from processes/pages
# see `src/lib/event_manager` for generated events
#
# it should return a list of events to happen
#

run_os = RunOs()
10 changes: 10 additions & 0 deletions run-auto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import subprocess
import sys

args = sys.argv[1:]

subprocess.run([
'python',
'auto.py',
*args
], cwd='src')
3 changes: 2 additions & 1 deletion run-desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?

], cwd='src')
Loading