Skip to content

Commit

Permalink
feat: add scene programs state manager
Browse files Browse the repository at this point in the history
  • Loading branch information
nampereira committed Jan 23, 2025
1 parent be1ce76 commit 83386df
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 0 deletions.
65 changes: 65 additions & 0 deletions tools/scene-programs/pk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# listens to activity on a scene and kills programs when inactive
from arena import *
from scenestate import SceneState

DFT_INACTIVE_TIME_SEC = 5
DFT_SCENE_STATE_UPDATE_SEC = 60

def scene_inactive(scene):

if not scene:
raise ValueError("Scene not set; Did you call SceneState's set_scene()?")
return

programs = [v for k,v in scene.all_objects.items() if hasattr(v, "type") and v.type == "program"]

for prgrm in programs:
try:
if prgrm.data.parent: # delete programs setup to run in a runtime
print(f"DELETING: {prgrm.data.name} ({prgrm.object_id})")
scene.delete_program(prgrm)
except ValueError:
pass
except AttributeError:
pass

def main(state_update_sec, inactive_time_sec):

# call inactive_callback after detecting the scene is inactive for
# inactive_time_secs; program will exit afterwards (exit_on_inactive=True)
scene_state = SceneState(
inactive_callback=scene_inactive,
inactive_time_secs=inactive_time_sec,
exit_on_inactive=True)

# create scene; user join/left callbacks handled by corresponding SceneState methods
scene = Scene(...,
user_join_callback=lambda s, c, m : scene_state.user_joined(c),
user_left_callback=lambda s, c, m : scene_state.user_left(c.object_id))

scene_state.set_scene(scene)

# update scene state every scene_state_update_sec
# or inactive_time_sec / 2, if smaller; never faster than every second
update_interval_sec = max(min(state_update_sec, inactive_time_sec / 2), 1)
print(f"Updating scene state every {update_interval_sec} seconds")
scene.run_forever(scene_state.update, update_interval_sec * 1000)
scene.run_tasks()

if __name__ == "__main__":
""" Parse arguments; start main """

parser = argparse.ArgumentParser()
parser.add_argument(
"--state_update_sec", default=os.environ.get('SCENE_STATE_UPDATE_MS', DFT_SCENE_STATE_UPDATE_SEC),
help="Specify the scene state update interval in seconds (can also be specified using SCENE_STATE_UPDATE_MS environment variable)")
parser.add_argument(
"--inactive_time_sec", default=os.environ.get('INACTIVE_TIME_SEC', DFT_INACTIVE_TIME_SEC),
help="Specify the scene inactivity time until a scene is said to be inactive in seconds (can also be specified using INACTIVE_TIME_SEC environment variable)")
args=parser.parse_args()

try:
main(**vars(args))
except Exception as e:
print(str(e))

101 changes: 101 additions & 0 deletions tools/scene-programs/scenestate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import time
import sys
from datetime import datetime

class SceneState():

DFT_INACTIVE_TIME_SECS=3600

def __init__(self, scene=None, inactive_callback=None, inactive_time_secs=DFT_INACTIVE_TIME_SECS, active_on_new_users=False, exit_on_inactive=True):
self.scene = scene
self.inactive_callback = inactive_callback
self.inactive_time_secs = inactive_time_secs
self.users_cam = dict()
self._active = True
self._active_ts = datetime.now()
self.active_on_new_users = active_on_new_users
self.exit_on_inactive = exit_on_inactive

@property
def active(self):
return self._active

@active.setter
def active(self, value):
self._active = value
self._active_ts = datetime.now()

def set_scene(self, scene):
self.scene = scene

def user_joined(self, camera):
user_cam = UserCameraState(camera)
self.users_cam[user_cam.id] = user_cam
if self.active_on_new_users: self.active = True

def user_left(self, id):
user_cam = self.users_cam[id]
self.users_cam.pop(user_cam.id)
return user_cam

def list_users(self):
return self.users_cam.items()

def update(self):
active_users = False
for id, user_cam in self.users_cam.items():
active_users = active_users or user_cam.update_state()

if self.active:
if active_users: self.active = True

dif = (datetime.now() - self._active_ts).total_seconds()
if dif > self.inactive_time_secs:
self.active = False
if self.inactive_callback: self.inactive_callback(self.scene)
else:
if self.exit_on_inactive:
if self.scene: self.scene.exit()
else: sys.exit(0)


def scene_inactive_callback(scene):
print("Inactive!")
if not scene: return
print()

class UserCameraState:

DFT_INACTIVE_TIME_SECS=600

def __init__(self, camera, inactive_time_secs=DFT_INACTIVE_TIME_SECS):
self.id = camera.object_id
self.camera = camera
self.prev_pos = None
self.active = True
self.active_ts = datetime.now()
self.inactive_time_secs = inactive_time_secs

def curr_pos(self):
return self.camera.data.position

def moved(self, min_displacement=0.5, update_pos=True):
displacement = 0
if self.prev_pos:
displacement = self.prev_pos.distance_to(self.curr_pos())
if (update_pos): self.prev_pos = self.curr_pos()
if (displacement > min_displacement): return True
return False

def update_state(self):
if self.moved():
self.active = True
self.active_ts = datetime.now()

if (self.active):
dif = (datetime.now() - self.active_ts).total_seconds()
if dif > self.inactive_time_secs:
self.active = False

return self.active

0 comments on commit 83386df

Please sign in to comment.