Commit 3e7594e5 authored by David Trattnig's avatar David Trattnig
Browse files

Fix for preloading. #78

parent a9d38e79
...@@ -184,21 +184,23 @@ class EngineExecutor(Timer): ...@@ -184,21 +184,23 @@ class EngineExecutor(Timer):
Primarily used for automations performed by the scheduler. Primarily used for automations performed by the scheduler.
""" """
_lock = None timer_store: dict = {}
logger = logging.getLogger("AuraEngine") logger = logging.getLogger("AuraEngine")
initialized = None EVENT_ON_READY = "on_ready"
timer_store = {}
parent_timer = None _lock = None
child_timer = None direct_exec: bool = None
direct_exec = None parent_timer: Timer = None
timer_id = None child_timer: Timer = None
timer_type = None timer_id: str = None
timer_type: str = None
func = None
param = None param = None
diff = None diff = None
dt = None dt = None
def __init__(self, timer_type="BASE", parent_timer=None, due_time=None, func=None, param=None): def __init__(self, timer_type:str="BASE", parent_timer:Timer=None, due_time=None, func=None, param=None):
""" """
Constructor Constructor
...@@ -209,7 +211,6 @@ class EngineExecutor(Timer): ...@@ -209,7 +211,6 @@ class EngineExecutor(Timer):
func (function): The function to be called func (function): The function to be called
param (object): Parameter passt to the function param (object): Parameter passt to the function
""" """
self.initialized = False
self._lock = Lock() self._lock = Lock()
from src.engine import Engine from src.engine import Engine
now_unix = Engine.engine_time() now_unix = Engine.engine_time()
...@@ -224,9 +225,8 @@ class EngineExecutor(Timer): ...@@ -224,9 +225,8 @@ class EngineExecutor(Timer):
self.timer_type = timer_type self.timer_type = timer_type
self.timer_id = f"{timer_type}:{func.__name__}:{due_time}" self.timer_id = f"{timer_type}:{func.__name__}:{due_time}"
if not due_time: diff = 0
diff = 0 if due_time:
else:
diff = due_time - now_unix diff = due_time - now_unix
self.diff = diff self.diff = diff
...@@ -240,32 +240,22 @@ class EngineExecutor(Timer): ...@@ -240,32 +240,22 @@ class EngineExecutor(Timer):
else: else:
if diff < 0: if diff < 0:
msg = f"Timer '{self.timer_id}' is due in the past. Executing immediately ..." msg = f"Timer '{self.timer_id}' is due in the past. Executing immediately ..."
self.logger.error(SU.red(msg)) self.logger.warn(SU.yellow(msg))
self.exec_now() self.exec_now()
elif diff == 0: elif diff == 0:
self.logger.info(f"Timer '{self.timer_id}' to be executed immediately") self.logger.debug(f"Timer '{self.timer_id}' to be executed immediately")
self.exec_now() self.exec_now()
else: else:
self.logger.debug(f"Timer '{self.timer_id}' to be executed in default manner")
self.exec_timed() self.exec_timed()
self.start()
def on_ready(self, func):
"""
Calls the passed function `func` when the timer is ready.
"""
while self.initialized == False:
timer.sleep(0.001)
self.logger.info(SU.orange("Waiting until the EngineExecutor is done with initialization..."))
if not self.direct_exec: #TODO Evaluate if we should join for direct exec too
self.join()
func()
def wait_for_parent(self): def wait_for_parent(self):
""" """
Child timers are dependend on their parents. So let's wait until parents are done with their stuff. @private
Child timers are dependend on their parents. So let's wait until parents are done with their stuff => finished execution.
Checks the parent state to be finished every 0.2 seconds.
""" """
if self.parent_timer: if self.parent_timer:
while self.parent_timer.is_alive(): while self.parent_timer.is_alive():
...@@ -275,22 +265,27 @@ class EngineExecutor(Timer): ...@@ -275,22 +265,27 @@ class EngineExecutor(Timer):
def exec_now(self): def exec_now(self):
""" """
@private
Immediate execution within a thread. It's not stored in the timer store. Immediate execution within a thread. It's not stored in the timer store.
Assigns the `timer_id` as the thread name. It also assigns the `timer_id` as the thread name.
""" """
self.direct_exec = True self.direct_exec = True
self.wait_for_parent() self.wait_for_parent()
thread = Thread(name=self.timer_id, target=self.func, args=(self.param,)) thread = Thread(name=self.timer_id, target=self.func, args=(self.param,))
time.sleep(0.2)
thread.start() thread.start()
self.initialized = True
def exec_timed(self): def exec_timed(self):
""" """
Timed execution in a thread. @private
Assigns the `timer_id` as the thread name. Timed execution in a thread. This method instroduces a slight delay to ensure
the thread is properly initialized before starting it.
It also assigns the `timer_id` as the thread name.
""" """
def wrapper_func(param=None): def wrapper_func(param=None):
self.wait_for_parent() self.wait_for_parent()
...@@ -298,18 +293,22 @@ class EngineExecutor(Timer): ...@@ -298,18 +293,22 @@ class EngineExecutor(Timer):
else: self.func() else: self.func()
super().__init__(self.diff, wrapper_func, (self.param,)) super().__init__(self.diff, wrapper_func, (self.param,))
self._name = self.timer_id self._name = self.timer_id
self.initialized = True time.sleep(0.2)
self.start()
def update_store(self): def update_store(self):
""" """
@private
Adds the instance to the store and cancels any previously existing commands. Adds the instance to the store and cancels any previously existing commands.
If a timer with the given ID is already existing but also already executed, If a timer with the given ID is already existing but also already executed,
then it is not added to the store. In such case the method returns `False`. then it is not added to the store. In such case the method returns `False`.
Returns: Returns:
(Boolean): True if the timer has been added to the store (Boolean): True if the timer has been added to the store. False if the
timer is already existing but dead.
""" """
with self._lock: with self._lock:
existing_command = None existing_command = None
...@@ -337,6 +336,8 @@ class EngineExecutor(Timer): ...@@ -337,6 +336,8 @@ class EngineExecutor(Timer):
def is_alive(self): def is_alive(self):
""" """
@private
Returns true if the command is still due to be executed. Returns true if the command is still due to be executed.
""" """
if self.direct_exec == True: if self.direct_exec == True:
...@@ -346,7 +347,7 @@ class EngineExecutor(Timer): ...@@ -346,7 +347,7 @@ class EngineExecutor(Timer):
def __str__(self): def __str__(self):
""" """
String represenation of the timer. String representation of the timer.
""" """
return f"[{self.timer_id}] exec at {str(self.dt)} (alive: {self.is_alive()})" return f"[{self.timer_id}] exec at {str(self.dt)} (alive: {self.is_alive()})"
...@@ -372,7 +373,7 @@ class EngineExecutor(Timer): ...@@ -372,7 +373,7 @@ class EngineExecutor(Timer):
@staticmethod @staticmethod
def log_commands(): def log_commands():
""" """
Prints a list of recent active and inactive timers. Prints a list of recent active and inactive timers to the logger.
""" """
msg = SU.blue("\n [ ENGINE COMMAND QUEUE ]\n") msg = SU.blue("\n [ ENGINE COMMAND QUEUE ]\n")
EngineExecutor.remove_stale_timers() EngineExecutor.remove_stale_timers()
...@@ -383,7 +384,7 @@ class EngineExecutor(Timer): ...@@ -383,7 +384,7 @@ class EngineExecutor(Timer):
else: else:
for timer in timers: for timer in timers:
if not timer.parent_timer: if not timer.parent_timer:
line = f" => {str(timer)}\n"# line = f" => {str(timer)}\n"
if timer.is_alive(): if timer.is_alive():
line = SU.green(line) line = SU.green(line)
msg += line msg += line
......
...@@ -45,17 +45,16 @@ class AuraScheduler(threading.Thread): ...@@ -45,17 +45,16 @@ class AuraScheduler(threading.Thread):
- Executes engine actions in an automated fashion - Executes engine actions in an automated fashion
""" """
config = None config:AuraConfig = None
logger = None logger = None
engine = None engine:Engine = None
exit_event = None exit_event:threading.Event = None
timeslot_renderer = None timeslot_renderer:TimeslotRenderer = None
programme = None programme:ProgrammeService = None
message_timer = [] message_timer = []
fallback = None fallback = None
is_initialized = None is_initialized:bool = None
is_initialized = None is_engine_ready:bool = None
def __init__(self, engine, fallback_manager): def __init__(self, engine, fallback_manager):
...@@ -407,10 +406,11 @@ class TimeslotCommand(EngineExecutor): ...@@ -407,10 +406,11 @@ class TimeslotCommand(EngineExecutor):
""" """
Command for triggering start and end of timeslot events. Command for triggering start and end of timeslot events.
""" """
engine = None engine: Engine = None
config = None config: AuraConfig = None
def __init__(self, engine, timeslot): def __init__(self, engine: Engine, timeslot):
""" """
Constructor Constructor
...@@ -421,12 +421,15 @@ class TimeslotCommand(EngineExecutor): ...@@ -421,12 +421,15 @@ class TimeslotCommand(EngineExecutor):
self.config = AuraConfig() self.config = AuraConfig()
self.engine = engine self.engine = engine
now_unix = SU.timestamp()
fade_out_time = float(self.config.get("fade_out_time")) fade_out_time = float(self.config.get("fade_out_time"))
start_fade_out = timeslot.end_unix - fade_out_time start_fade_in = timeslot.start_unix - now_unix
self.logger.info(f"Fading out timeslot in {start_fade_out} seconds at {timeslot.timeslot_end} | Timeslot: {timeslot}") start_fade_out = timeslot.end_unix - now_unix - fade_out_time
# Initialize the "fade in" EngineExecuter and instatiate a connected child EngineExecuter for "fade out" when the parent is ready self.logger.debug(f"Fading in timeslot in {start_fade_in} seconds at {SU.fmt_time(timeslot.start_unix)} | Timeslot: {timeslot}")
self.logger.debug(f"Fading out timeslot in {start_fade_out} seconds at {SU.fmt_time(timeslot.end_unix - fade_out_time)} | Timeslot: {timeslot}")
# Initialize the "fade in" EngineExecuter and instantiate a connected child EngineExecuter for "fade out" when the parent is ready
super().__init__("TIMESLOT", None, timeslot.start_unix, self.do_start_timeslot, timeslot) super().__init__("TIMESLOT", None, timeslot.start_unix, self.do_start_timeslot, timeslot)
self.on_ready(lambda: EngineExecutor("TIMESLOT", self, start_fade_out, self.do_end_timeslot, timeslot)) EngineExecutor("TIMESLOT", self, timeslot.end_unix - fade_out_time, self.do_end_timeslot, timeslot)
def do_start_timeslot(self, timeslot): def do_start_timeslot(self, timeslot):
...@@ -456,10 +459,11 @@ class PlayCommand(EngineExecutor): ...@@ -456,10 +459,11 @@ class PlayCommand(EngineExecutor):
""" """
Command for triggering timed preloading and playing as a child command. Command for triggering timed preloading and playing as a child command.
""" """
engine = None engine: Engine = None
config = None config: AuraConfig = None
def __init__(self, engine, entries): def __init__(self, engine: Engine, entries):
""" """
Constructor Constructor
...@@ -470,11 +474,13 @@ class PlayCommand(EngineExecutor): ...@@ -470,11 +474,13 @@ class PlayCommand(EngineExecutor):
self.config = AuraConfig() self.config = AuraConfig()
self.engine = engine self.engine = engine
start_preload = entries[0].start_unix - self.config.get("preload_offset") preload_offset = self.config.get("preload_offset")
start_preload = entries[0].start_unix - preload_offset
start_play = entries[0].start_unix start_play = entries[0].start_unix
self.logger.debug(f"Preloading entries at {SU.fmt_time(start_preload)}, {preload_offset} seconds before playing it at {SU.fmt_time(start_play)}")
# Initialize the "preload" EngineExecuter and attach a child `PlayCommand` to the "on_ready" event handler # Initialize the "preload" EngineExecuter and attach a child `PlayCommand` to the "on_ready" event handler
preload_timer = super().__init__("PRELOAD", None, start_preload, self.do_preload, entries) super().__init__("PRELOAD", None, start_preload, self.do_preload, entries)
self.on_ready(lambda: EngineExecutor("PLAY", self, start_play, self.do_play, entries)) EngineExecutor("PLAY", self, start_play, self.do_play, entries)
def do_preload(self, entries): def do_preload(self, entries):
...@@ -504,7 +510,7 @@ class PlayCommand(EngineExecutor): ...@@ -504,7 +510,7 @@ class PlayCommand(EngineExecutor):
# Let 'em play anyway ... # Let 'em play anyway ...
self.logger.critical(SU.red("PLAY: The entry/entries are not yet ready to be played (Entries: %s)" % ResourceUtil.get_entries_string(entries))) self.logger.critical(SU.red("PLAY: The entry/entries are not yet ready to be played (Entries: %s)" % ResourceUtil.get_entries_string(entries)))
while (entries[-1].status != EntryPlayState.READY): while (entries[-1].status != EntryPlayState.READY):
self.logger.info("PLAY: Wait a little until preloading is done ...") self.logger.info("PLAY: Wait a little bit until preloading is done ...")
time.sleep(2) time.sleep(2)
self.engine.player.play(entries[0], TransitionType.FADE) self.engine.player.play(entries[0], TransitionType.FADE)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment