feat: add rollback button

This commit is contained in:
2025-10-24 19:27:58 +02:00
parent 8b7927a3c5
commit f1fadd123f
5 changed files with 70 additions and 12 deletions

View File

@@ -8,7 +8,8 @@ from src.remote_controller import RemoteController
from src.utils import get_segments_intersection, segments_intersect
from src.vec import Vec
sign = lambda x: 0 if x == 0 else (-1 if x < 0 else 1)
def sign(x): return 0 if x == 0 else (-1 if x < 0 else 1)
class Car:
@@ -27,6 +28,8 @@ class Car:
RAYS_MAX_DIST = 100
def __init__(self, pos: Vec, direction: Vec) -> None:
self.initial_pos: Vec = pos.copy()
self.initial_dir: Vec = direction.copy()
self.pos: Vec = pos
self.direction: Vec = direction
self.speed: float = 0
@@ -77,7 +80,8 @@ class Car:
if show_raycasts:
pos: Vec = camera.world2screen(self.pos)
for p in self.rays_end:
pygame.draw.line(surf, (255, 0, 0), pos, camera.world2screen(p), 2)
pygame.draw.line(surf, (255, 0, 0), pos,
camera.world2screen(p), 2)
pts: list[Vec] = self.get_corners()
pts = [camera.world2screen(p) for p in pts]
@@ -127,14 +131,17 @@ class Car:
n *= -1
dist = -dist
self.speed = 0
self.pos = self.pos + n * (self.COLLISION_MARGIN - dist)
self.pos = self.pos + n * \
(self.COLLISION_MARGIN - dist)
return
def cast_rays(self, polygons: list[list[Vec]]):
for i in range(self.N_RAYS):
angle: float = radians((i / (self.N_RAYS - 1) - 0.5) * self.RAYS_FOV)
angle: float = radians(
(i / (self.N_RAYS - 1) - 0.5) * self.RAYS_FOV)
p: Optional[Vec] = self.cast_ray(angle, polygons)
self.rays[i] = self.RAYS_MAX_DIST if p is None else (p - self.pos).mag()
self.rays[i] = self.RAYS_MAX_DIST if p is None else (
p - self.pos).mag()
self.rays_end[i] = self.pos if p is None else p
def cast_ray(self, angle: float, polygons: list[list[Vec]]) -> Optional[Vec]:
@@ -161,3 +168,8 @@ class Car:
dist = d
closest = p
return closest
def reset(self):
self.pos = self.initial_pos.copy()
self.direction = self.initial_dir.copy()
self.speed = 0

View File

@@ -5,10 +5,14 @@ from enum import IntEnum
import struct
from typing import Type
from src.snapshot import Snapshot
class CommandType(IntEnum):
CAR_CONTROL = 0
RECORDING = 1
APPLY_SNAPSHOT = 2
RESET = 3
class CarControl(IntEnum):
@@ -30,8 +34,8 @@ class Command(abc.ABC):
)
Command.REGISTRY[cls.TYPE] = cls
@abc.abstractmethod
def get_payload(self) -> bytes: ...
def get_payload(self) -> bytes:
return b""
def pack(self) -> bytes:
payload: bytes = self.get_payload()
@@ -43,8 +47,8 @@ class Command(abc.ABC):
return Command.REGISTRY[type].from_payload(data[1:])
@classmethod
@abc.abstractmethod
def from_payload(cls, payload: bytes) -> Command: ...
def from_payload(cls, payload: bytes) -> Command:
return cls()
class ControlCommand(Command):
@@ -82,3 +86,24 @@ class RecordingCommand(Command):
def from_payload(cls, payload: bytes) -> Command:
state: bool = struct.unpack(">B", payload)[0]
return RecordingCommand(state)
class ApplySnapshotCommand(Command):
TYPE = CommandType.APPLY_SNAPSHOT
__match_args__ = ("snapshot",)
def __init__(self, snapshot: Snapshot) -> None:
super().__init__()
self.snapshot: Snapshot = snapshot
def get_payload(self) -> bytes:
return self.snapshot.pack()
@classmethod
def from_payload(cls, payload: bytes) -> Command:
snapshot: Snapshot = Snapshot.unpack(payload)
return ApplySnapshotCommand(snapshot)
class ResetCommand(Command):
TYPE = CommandType.RESET

View File

@@ -9,7 +9,7 @@ from PyQt6.QtCore import QObject, QThread, QTimer, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QKeyEvent
from PyQt6.QtWidgets import QMainWindow
from src.command import CarControl, Command, ControlCommand, RecordingCommand
from src.command import ApplySnapshotCommand, CarControl, Command, ControlCommand, RecordingCommand, ResetCommand
from src.record_file import RecordFile
from src.recorder_ui import Ui_Recorder
from src.snapshot import Snapshot
@@ -203,7 +203,19 @@ class RecorderWindow(Ui_Recorder, QMainWindow):
self.send_command(RecordingCommand(self.recording))
def rollback(self):
pass
rollback_by: int = self.forgetSnapshotNumber.value()
rollback_by = max(0, min(rollback_by, len(self.snapshots) - 1))
self.snapshots = self.snapshots[:-rollback_by]
self.nbrSnapshotSaved.setText(str(len(self.snapshots)))
if len(self.snapshots) == 0:
self.send_command(ResetCommand())
else:
self.send_command(ApplySnapshotCommand(self.snapshots[-1]))
if self.recording:
self.toggle_record()
def toggle_autopilot(self):
self.autopiloting = not self.autopiloting

View File

@@ -6,7 +6,7 @@ import struct
import threading
from typing import TYPE_CHECKING, Optional
from src.command import CarControl, Command, ControlCommand, RecordingCommand
from src.command import ApplySnapshotCommand, CarControl, Command, ControlCommand, RecordingCommand, ResetCommand
from src.snapshot import Snapshot
from src.utils import RepeatTimer
@@ -119,6 +119,10 @@ class RemoteController:
self.set_control(control, active)
case RecordingCommand(state):
self.recording = state
case ApplySnapshotCommand(snapshot):
snapshot.apply(self.car)
case ResetCommand():
self.car.reset()
def set_control(self, control: CarControl, active: bool):
setattr(self.car, self.CONTROL_ATTRIBUTES[control], active)

View File

@@ -94,3 +94,8 @@ class Snapshot:
raycast_distances=car.rays.copy(),
image=None
)
def apply(self, car: Car):
car.pos = self.position.copy()
car.direction = self.direction.copy()
car.speed = 0