Files
2026-07-13 12:24:33 +08:00

146 lines
4.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Standard
from enum import Enum, auto
import asyncio
import threading
class EventType(Enum):
LOADING = auto()
class EventStatus(Enum):
ONGOING = auto()
DONE = auto()
NOT_FOUND = auto()
class EventManager:
"""
A thread-safe event manager to manage asynchronous events.
Each event is identified by its type and a unique id.
Events are organized by status for efficient counting.
"""
def __init__(self) -> None:
# Guard by lock
# Structure: events[event_type][event_status][event_id] = future
self.events: dict[EventType, dict[EventStatus, dict[str, asyncio.Future]]] = {
et: {es: {} for es in EventStatus} for et in EventType
}
self.lock = threading.Lock()
def add_event(
self,
event_type: EventType,
event_id: str,
future: asyncio.Future,
) -> None:
"""
Add an event with the given type and id.
"""
with self.lock:
status_dict = self.events.get(event_type, None)
assert status_dict is not None, (
f"Invalid event type {event_type} in EventManager."
)
status_dict[EventStatus.ONGOING][event_id] = future
def pop_event(
self,
event_type: EventType,
event_id: str,
) -> asyncio.Future:
"""
Pop and return the event with the given type and id.
"""
with self.lock:
status_dict = self.events.get(event_type, None)
assert status_dict is not None, (
f"Invalid event type {event_type} in EventManager."
)
done_events = status_dict[EventStatus.DONE]
assert event_id in done_events, (
f"Event {event_id} of type {event_type} is not done or not found."
)
return done_events.pop(event_id)
def update_event_status(
self,
event_type: EventType,
event_id: str,
status: EventStatus,
) -> None:
"""
Update the status of the event with the given type and id.
"""
with self.lock:
status_dict = self.events.get(event_type, None)
assert status_dict is not None, (
f"Invalid event type {event_type} in EventManager."
)
# Find the event in any status dict
event = None
for s in EventStatus:
if event_id in status_dict[s]:
event = status_dict[s].pop(event_id)
break
if event is None:
raise KeyError(f"Event {event_id} of type {event_type} not found.")
# Move to new status dict
status_dict[status][event_id] = event
def get_event_status(
self,
event_type: EventType,
event_id: str,
) -> EventStatus:
"""
Get the status of the event with the given type and id.
"""
with self.lock:
status_dict = self.events.get(event_type, None)
assert status_dict is not None, (
f"Invalid event type {event_type} in EventManager."
)
for status in EventStatus:
if event_id in status_dict[status]:
return status
return EventStatus.NOT_FOUND
def get_events_count_by_status(
self,
event_type: EventType,
status: EventStatus,
) -> int:
"""
Get the count of events for the given event type and status.
This is a lightweight O(1) operation using dict length.
"""
with self.lock:
status_dict = self.events.get(event_type, None)
if status_dict is None:
return 0
return len(status_dict[status])
def get_event_future(
self,
event_type: EventType,
event_id: str,
) -> asyncio.Future:
"""
Pop and return the event with the given type and id.
"""
with self.lock:
status_dict = self.events.get(event_type, None)
assert status_dict is not None, (
f"Invalid event type {event_type} in EventManager."
)
done_events = status_dict[EventStatus.DONE]
assert event_id in done_events, (
f"Event {event_id} of type {event_type} is not done or not found."
)
return done_events[event_id]