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 swap delay #165

Merged
merged 31 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9f1da9a
Add swap_delay attribute to config
plbrault Jul 3, 2024
bd9b844
Add swapping_to and started_swapping_at properties
plbrault Jul 3, 2024
8d16556
WIP swap-delay
plbrault Jul 3, 2024
976ea87
Use swapping_in_progress in handle_page_swaps
plbrault Jul 3, 2024
f07405d
Display page in different color while swapping
plbrault Jul 3, 2024
469c7fe
Change color of pages during swap
plbrault Jul 3, 2024
d13162f
Add swap progress bar
plbrault Jul 3, 2024
d57f7c4
Rename in_swap to on_disk
plbrault Jul 4, 2024
ee1fdfa
Add swap_requested property
plbrault Jul 4, 2024
0aff9b3
Use swap_requested where appropriate
plbrault Jul 4, 2024
aec6f1d
Remove useless _swap_is_enabled attribute
plbrault Jul 4, 2024
a37f2e2
Refactor swap delay
plbrault Jul 5, 2024
b61e266
Bring back PageManager's update method
plbrault Jul 5, 2024
ff80967
Implement swap queue
plbrault Jul 5, 2024
22387da
Make default swap delay faster
plbrault Jul 5, 2024
d1ac99b
Fix pylint
plbrault Jul 5, 2024
74a9a2c
Remove unnecessary public properties in Page
plbrault Jul 8, 2024
89fc55e
Remove useless queued parameter and WIP test new page swap methods
plbrault Jul 8, 2024
68b5e83
Refactor swap percentage completed
plbrault Jul 8, 2024
8b1361f
Fix page swap test
plbrault Jul 8, 2024
4afc4af
Fix first test of page_manager
plbrault Jul 8, 2024
a4d26fb
Update PageManager swap test
plbrault Jul 8, 2024
b5ebb0d
Fix test_swap_whole_row
plbrault Jul 22, 2024
8040342
Fix test_set_page_to_swap_while_running
plbrault Jul 22, 2024
dc50c93
Update test_set_page_to_swap_before_running
plbrault Jul 22, 2024
a46eeb1
Update test_remove_page_from_swap_while_running
plbrault Jul 22, 2024
42dc654
Update test_yield_cpu_while_waiting_for_page
plbrault Jul 22, 2024
3cd1ca3
Update test_starvation_while_waiting_for_page
plbrault Jul 22, 2024
f8bd8bb
Update test_page_deletion_when_process_is_killed
plbrault Jul 22, 2024
7552517
Fix test_blinking_animation_deactivation
plbrault Jul 22, 2024
15d1e3b
Improve _update_swap comment
plbrault Jul 22, 2024
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
2 changes: 1 addition & 1 deletion automated_skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
class Page:
pid: int
idx: int
in_swap: bool
on_disk: bool
in_use: bool

@property
Expand Down
74 changes: 65 additions & 9 deletions src/game_objects/page.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from typing import Optional

from engine.game_event_type import GameEventType
from engine.game_object import GameObject
import game_monitor
from game_objects.page_slot import PageSlot
from game_objects.views.page_view import PageView

_BLINKING_INTERVAL_MS = 200
Expand All @@ -10,9 +14,17 @@ def __init__(self, pid, idx, page_manager):
self._pid = pid
self._idx = idx
self._page_manager = page_manager
self._stage = page_manager.stage

self._in_use = False
self._in_swap = False

self._waiting_to_swap: bool = False
self._swapping_from: Optional[PageSlot] = None
self._swapping_to: Optional[PageSlot] = None
self._started_swap_at: Optional[int] = None
self._swap_percentage_completed: float = 0
self._on_disk = False

self._display_blink_color = False

super().__init__(PageView(self))
Expand All @@ -34,34 +46,78 @@ def in_use(self, value):
self._in_use = value

@property
def in_swap(self):
return self._in_swap
def swap_in_progress(self) -> bool:
return self._started_swap_at is not None

@property
def swap_requested(self) -> bool:
return self._waiting_to_swap or self.swap_in_progress

@in_swap.setter
def in_swap(self, value):
self._in_swap = value
@property
def swap_percentage_completed(self) -> float:
return self._swap_percentage_completed

@property
def on_disk(self):
return self._on_disk

@on_disk.setter
def on_disk(self, value):
self._on_disk = value

@property
def display_blink_color(self):
return self._display_blink_color

def swap(self, swap_whole_row : bool = False):
def request_swap(self, swap_whole_row : bool = False):
"""The method called when the player clicks on the page."""
self._page_manager.swap_page(self, swap_whole_row)

def init_swap(self, swapping_from : PageSlot, swapping_to : PageSlot):
"""The method called by the page manager to set the swap attributes."""
self._swapping_from = swapping_from
self._swapping_to = swapping_to
self._waiting_to_swap = True
self._swap_percentage_completed = 0

def start_swap(self, current_time: int):
"""The method called by the page manager to actually start the swap."""
self._waiting_to_swap = False
self._started_swap_at = current_time

def _update_swap(self, current_time):
"""This method is called at each update. If a swap is in progress, it performs
appropriate checks and operations."""
if self.swap_in_progress:
self._swap_percentage_completed = min(1,
(current_time - self._started_swap_at)
/ self._stage.config.swap_delay_ms
)
if self._swap_percentage_completed == 1:
self.view.set_xy(self._swapping_to.view.x, self._swapping_to.view.y)
self._swapping_from.page = None
self._swapping_from = None
self._swapping_to = None
self._started_swap_at = None
self._on_disk = not self._on_disk
self._swap_percentage_completed = 0
game_monitor.notify_page_swap(self.pid, self.idx, self.on_disk)

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, shift_down : bool):
self.swap(shift_down)
self.request_swap(shift_down)

def update(self, current_time, events):
for event in events:
if self._check_if_clicked_on(event):
self._on_click(event.get_property('shift'))

if self.in_use and self.in_swap:
self._update_swap(current_time)
if self.in_use and self.on_disk:
self._display_blink_color = int(current_time / _BLINKING_INTERVAL_MS) % 2 == 1
else:
self._display_blink_color = False
77 changes: 48 additions & 29 deletions src/game_objects/page_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import game_monitor
from queue import Queue

from engine.game_object import GameObject
from game_objects.views.page_manager_view import PageManagerView
from game_objects.page import Page
Expand All @@ -14,10 +15,10 @@ def __init__(self, stage):
self._ram_slots = []
self._swap_slots = []
self._pages = {}
self._swap_queue = Queue()

self._pages_in_ram_label_xy = (0, 0)
self._swap_is_enabled = True
self._pages_in_swap_label_xy = None
self._pages_on_disk_label_xy = None

super().__init__(PageManagerView(self))

Expand All @@ -29,13 +30,17 @@ def get_total_rows(cls):
def get_num_cols(cls):
return cls._NUM_COLS

@property
def stage(self):
return self._stage

@property
def pages_in_ram_label_xy(self):
return self._pages_in_ram_label_xy

@property
def pages_in_swap_label_xy(self):
return self._pages_in_swap_label_xy
def pages_on_disk_label_xy(self):
return self._pages_on_disk_label_xy

def get_page(self, pid, idx):
return self._pages[(pid, idx)]
Expand All @@ -60,7 +65,7 @@ def setup(self):
self.children.extend(self._ram_slots)

if num_swap_rows > 0:
self._pages_in_swap_label_xy = (
self._pages_on_disk_label_xy = (
self._stage.process_manager.view.width,
164 + num_ram_rows * PageSlot().view.height + num_ram_rows * 5)

Expand All @@ -69,13 +74,11 @@ def setup(self):
swap_slot = PageSlot()
x = self._stage.process_manager.view.width + \
column * ram_slot.view.width + column * 5
y = self._pages_in_swap_label_xy[1] + \
y = self._pages_on_disk_label_xy[1] + \
35 + row * ram_slot.view.height + row * 5
swap_slot.view.set_xy(x, y)
self._swap_slots.append(swap_slot)
self.children.extend(self._swap_slots)
else:
self._swap_is_enabled = False

def create_page(self, pid, idx):
page = Page(pid, idx, self)
Expand All @@ -90,7 +93,7 @@ def create_page(self, pid, idx):
for swap_slot in self._swap_slots:
if not swap_slot.has_page:
swap_slot.page = page
page.in_swap = True
page.on_disk = True
page.view.set_xy(swap_slot.view.x, swap_slot.view.y)
page_created = True
break
Expand All @@ -99,36 +102,42 @@ def create_page(self, pid, idx):
return page

def swap_page(self, page : Page, swap_whole_row : bool = False):
source_slots = self._swap_slots if page.in_swap else self._ram_slots
target_slots = self._ram_slots if page.in_swap else self._swap_slots
if page.swap_requested:
return

source_slots = self._swap_slots if page.on_disk else self._ram_slots
target_slots = self._ram_slots if page.on_disk else self._swap_slots

can_swap = False
previous_slot = None
swapping_from = None
swapping_to = None

for ram_slot in target_slots:
if not ram_slot.has_page:
for source_slot in source_slots:
if source_slot.page == page:
swapping_from = source_slot
break
for target_slot in target_slots:
if not target_slot.has_page:
can_swap = True
swapping_to = target_slot
break
if can_swap:
for source_slot in source_slots:
if source_slot.page == page:
source_slot.page = None
previous_slot = source_slot
break
for target_slot in target_slots:
if not target_slot.has_page:
target_slot.page = page
page.view.set_xy(target_slot.view.x, target_slot.view.y)
break
page.in_swap = not page.in_swap
game_monitor.notify_page_swap(page.pid, page.idx, page.in_swap)
swapping_to.page = page

queue_swap = bool([page for page in self._pages.values() if page.swap_in_progress])
page.init_swap(swapping_from, swapping_to)
if queue_swap:
self._swap_queue.put(page)
else:
page.start_swap(self._stage.current_time)

if swap_whole_row:
slots_on_same_row = [
slot
for slot in source_slots
if (
slot.view.y == previous_slot.view.y
and slot != previous_slot
slot.view.y == swapping_from.view.y
and slot != swapping_from
)
]
for slot in slots_on_same_row:
Expand All @@ -146,3 +155,13 @@ def delete_page(self, page):
break
self.children.remove(page)
del self._pages[(page.pid, page.idx)]

def _handle_swap_queue(self):
if not self._swap_queue.empty():
if not bool([page for page in self._pages.values() if page.swap_in_progress]):
page = self._swap_queue.get()
page.start_swap(self._stage.current_time)

def update(self, current_time, events):
self._handle_swap_queue()
super().update(current_time, events)
16 changes: 8 additions & 8 deletions src/game_objects/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def use_cpu(self):
for i in range(num_pages):
page = self._page_manager.create_page(self._pid, i)
self._pages.append(page)
game_monitor.notify_page_new(page.pid, page.idx, page.in_swap, page.in_use)
game_monitor.notify_page_new(page.pid, page.idx, page.on_disk, page.in_use)
for page in self._pages:
page.in_use = True
game_monitor.notify_page_use(page.pid, page.idx, page.in_use)
Expand Down Expand Up @@ -255,13 +255,13 @@ def _handle_events(self, events):
if self._check_if_clicked_on(event):
self._on_click()

def _handle_pages_in_swap(self):
pages_in_swap = 0
def _handle_unavailable_pages(self):
unavailable_pages = 0
if self.has_cpu:
for page in self._pages:
if page.in_swap:
pages_in_swap += 1
self._set_waiting_for_page(pages_in_swap > 0)
if page.on_disk or page.swap_requested:
unavailable_pages += 1
self._set_waiting_for_page(unavailable_pages > 0)

def _update_starvation_level(self, current_time):
if self.has_cpu and not self.is_blocked:
Expand Down Expand Up @@ -297,7 +297,7 @@ def _handle_new_page_probability(self):
self._pages.append(new_page)
new_page.in_use = True
game_monitor.notify_page_new(
new_page.pid, new_page.idx, new_page.in_swap, new_page.in_use)
new_page.pid, new_page.idx, new_page.on_disk, new_page.in_use)

def _handle_graceful_termination_probability(self, current_time):
if self.has_cpu and not self.is_blocked:
Expand All @@ -319,7 +319,7 @@ def update(self, current_time, events):
self._handle_events(events)

if not self.has_ended:
self._handle_pages_in_swap()
self._handle_unavailable_pages()
if current_time >= self._last_event_check_time + ONE_SECOND:
self._last_event_check_time = current_time
self._update_starvation_level(current_time)
Expand Down
8 changes: 4 additions & 4 deletions src/game_objects/views/page_manager_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def __init__(self, page_manager):

self._pages_in_ram_text_surface = FONT_PRIMARY_LARGE.render(
'Memory Pages in RAM :', False, Color.WHITE)
self._pages_in_swap_space_text_surface = FONT_PRIMARY_LARGE.render(
self._pages_on_disk_space_text_surface = FONT_PRIMARY_LARGE.render(
'Memory Pages on Disk :', False, Color.WHITE)

@property
Expand All @@ -25,6 +25,6 @@ def height(self):
def draw(self, surface):
surface.blit(self._pages_in_ram_text_surface,
self._page_manager.pages_in_ram_label_xy)
if self._page_manager.pages_in_swap_label_xy is not None:
surface.blit(self._pages_in_swap_space_text_surface,
self._page_manager.pages_in_swap_label_xy)
if self._page_manager.pages_on_disk_label_xy is not None:
surface.blit(self._pages_on_disk_space_text_surface,
self._page_manager.pages_on_disk_label_xy)
14 changes: 13 additions & 1 deletion src/game_objects/views/page_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,22 @@ def height(self):

def draw(self, surface):
color = Color.DARK_GREY
if self._page.display_blink_color:
if self._page.swap_requested:
color = Color.TEAL
elif self._page.display_blink_color:
color = Color.BLUE
elif self._page.in_use:
color = Color.WHITE
pygame.draw.rect(surface, color, pygame.Rect(
self._x, self._y, self.width, self.height))
surface.blit(self._pid_text_surface, (self._x + 1, self._y + 5))

if self._page.swap_in_progress:
progress_bar_width = (self.width - 4) * self._page.swap_percentage_completed
progress_bar_height = 2
pygame.draw.rect(surface, Color.BLACK, pygame.Rect(
self._x + 2,
self._y + self.height - 4,
progress_bar_width,
progress_bar_height
))
2 changes: 1 addition & 1 deletion src/scenes/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def _process_script_events(self):
event['pid']).toggle()
elif event['type'] == 'page':
self._page_manager.get_page(
event['pid'], event['idx']).swap()
event['pid'], event['idx']).request_swap()
except Exception as exc: # pylint: disable=broad-exception-caught
print(exc.__class__.__name__, *exc.args, event, file=sys.stderr)

Expand Down
1 change: 1 addition & 0 deletions src/stage_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class StageConfig:
num_processes_at_startup: int = 14
max_processes: int = MAX_PROCESSES
num_ram_rows: int = 8
swap_delay_ms: int = 100
new_process_probability: float = 0.05
priority_process_probability: float = 0.01
io_probability: float = 0.01
Expand Down
Loading
Loading