clean: timers interface

feat: cancel autosave operator
This commit is contained in:
Swann 2020-12-22 16:04:50 +01:00
parent 03b92eb5e7
commit f043b03128
No known key found for this signature in database
GPG Key ID: E1D3641A7C43AACB
3 changed files with 104 additions and 79 deletions

View File

@ -42,21 +42,20 @@ import bpy
import mathutils
from bpy.app.handlers import persistent
from bpy_extras.io_utils import ExportHelper, ImportHelper
from replication.constants import (FETCHED, RP_COMMON, STATE_ACTIVE,
STATE_INITIAL, STATE_SYNCING, UP,
COMMITED)
from replication.constants import (COMMITED, FETCHED, RP_COMMON, STATE_ACTIVE,
STATE_INITIAL, STATE_SYNCING, UP)
from replication.data import ReplicatedDataFactory
from replication.exception import NonAuthorizedOperationError
from replication.interface import session
from . import bl_types, delayable, environment, ui, utils
from . import bl_types, environment, timers, ui, utils
from .presence import SessionStatusWidget, renderer, view3d_find
from .timers import registry
background_execution_queue = Queue()
deleyables = []
stop_modal_executor = False
def session_callback(name):
""" Session callback wrapper
@ -204,8 +203,8 @@ class SessionStartOperator(bpy.types.Operator):
if settings.update_method == 'DEFAULT':
if type_local_config.bl_delay_apply > 0:
deleyables.append(
delayable.ApplyTimer(
timout=type_local_config.bl_delay_apply,
timers.ApplyTimer(
timeout=type_local_config.bl_delay_apply,
target_type=type_module_class))
if bpy.app.version[1] >= 91:
@ -219,7 +218,7 @@ class SessionStartOperator(bpy.types.Operator):
external_update_handling=use_extern_update)
if settings.update_method == 'DEPSGRAPH':
deleyables.append(delayable.ApplyTimer(
deleyables.append(timers.ApplyTimer(
settings.depsgraph_update_rate/1000))
# Host a session
@ -270,12 +269,12 @@ class SessionStartOperator(bpy.types.Operator):
logging.error(str(e))
# Background client updates service
deleyables.append(delayable.ClientUpdate())
deleyables.append(delayable.DynamicRightSelectTimer())
deleyables.append(timers.ClientUpdate())
deleyables.append(timers.DynamicRightSelectTimer())
session_update = delayable.SessionStatusUpdate()
session_user_sync = delayable.SessionUserSync()
session_background_executor = delayable.MainThreadExecutor(
session_update = timers.SessionStatusUpdate()
session_user_sync = timers.SessionUserSync()
session_background_executor = timers.MainThreadExecutor(
execution_queue=background_execution_queue)
session_update.register()
@ -750,31 +749,65 @@ def dump_db(filepath):
filepath = Path(filepath)
filepath = filepath.with_name(f"{filepath.stem}_{stime}{filepath.suffix}")
with gzip.open(filepath, "wb") as f:
logging.info(f"Writing db snapshot to {filepath}")
logging.info(f"Writing session snapshot to {filepath}")
pickle.dump(db, f, protocol=4)
class SessionRecordGraphOperator(bpy.types.Operator, ExportHelper):
bl_idname = "session.export"
bl_label = "SessionRecordGraph"
class SessionSaveBackupOperator(bpy.types.Operator, ExportHelper):
bl_idname = "session.save"
bl_label = "Save session"
bl_description = "Save a snapshot of the collaborative session"
# ExportHelper mixin class uses this
filename_ext = ".db"
def execute(self, context):
recorder = delayable.SessionRecordGraphTimer(filepath=self.filepath)
recorder.register()
enable_autosave: bpy.props.BoolProperty(
name="Auto-save",
description="Enable session auto-save",
default=True,
)
save_interval: bpy.props.FloatProperty(
name="Auto save interval",
description="auto-save interval (seconds)",
default=10,
)
def execute(self, context):
if self.enable_autosave:
recorder = timers.SessionBackupTimer(
filepath=self.filepath,
timeout=self.save_interval)
recorder.register()
deleyables.append(recorder)
else:
dump_db(self.filepath)
return {'FINISHED'}
@classmethod
def poll(cls, context):
return session.state['STATE'] == STATE_ACTIVE
class SessionStopAutoSaveOperator(bpy.types.Operator):
bl_idname = "session.cancel_autosave"
bl_label = "Cancel auto-save"
bl_description = "Cancel session auto-save"
@classmethod
def poll(cls, context):
return (session.state['STATE'] == STATE_ACTIVE and 'SessionBackupTimer' in registry)
def execute(self, context):
autosave_timer = registry.get('SessionBackupTimer')
autosave_timer.unregister()
return {'FINISHED'}
class SessionLoadGraphOperator(bpy.types.Operator, ImportHelper):
bl_idname = "session.load"
bl_label = "SessionLoadGraph"
bl_description = "Load a Multi-user session save"
bl_options = {'REGISTER', 'UNDO'}
# ExportHelper mixin class uses this
@ -782,6 +815,7 @@ class SessionLoadGraphOperator(bpy.types.Operator, ImportHelper):
def execute(self, context):
from replication.graph import ReplicationGraph
# TODO: add filechecks
try:
@ -865,8 +899,9 @@ classes = (
SessionInitOperator,
SessionClearCache,
SessionNotifyOperator,
SessionRecordGraphOperator,
SessionSaveBackupOperator,
SessionLoadGraphOperator,
SessionStopAutoSaveOperator,
)

View File

@ -16,68 +16,48 @@
# ##### END GPL LICENSE BLOCK #####
import logging
import sys
import bpy
from . import utils
from .presence import (renderer,
UserFrustumWidget,
UserNameWidget,
UserSelectionWidget,
refresh_3d_view,
generate_user_camera,
get_view_matrix,
refresh_sidebar_view)
from . import operators
from replication.constants import (FETCHED,
UP,
RP_COMMON,
STATE_INITIAL,
STATE_QUITTING,
STATE_ACTIVE,
STATE_SYNCING,
STATE_LOBBY,
STATE_SRV_SYNC)
from replication.interface import session
from replication.constants import (FETCHED, RP_COMMON, STATE_ACTIVE,
STATE_INITIAL, STATE_LOBBY, STATE_QUITTING,
STATE_SRV_SYNC, STATE_SYNCING, UP)
from replication.exception import NonAuthorizedOperationError
from replication.interface import session
from . import operators, utils
from .presence import (UserFrustumWidget, UserNameWidget, UserSelectionWidget,
generate_user_camera, get_view_matrix, refresh_3d_view,
refresh_sidebar_view, renderer)
this = sys.modules[__name__]
# Registered timers
this.registry = dict()
def is_annotating(context: bpy.types.Context):
""" Check if the annotate mode is enabled
"""
return bpy.context.workspace.tools.from_space_view3d_mode('OBJECT', create=False).idname == 'builtin.annotate'
class Delayable():
"""Delayable task interface
"""
def register(self):
raise NotImplementedError
def execute(self):
raise NotImplementedError
def unregister(self):
raise NotImplementedError
class Timer(Delayable):
class Timer(object):
"""Timer binder interface for blender
Run a bpy.app.Timer in the background looping at the given rate
"""
def __init__(self, duration=1):
super().__init__()
self._timeout = duration
def __init__(self, timeout=10, id=None):
self._timeout = timeout
self.is_running = False
self.id = id if id else self.__class__.__name__
def register(self):
"""Register the timer into the blender timer system
"""
if not self.is_running:
this.registry[self.id] = self
bpy.app.timers.register(self.main)
self.is_running = True
logging.debug(f"Register {self.__class__.__name__}")
@ -105,23 +85,26 @@ class Timer(Delayable):
"""Unnegister the timer of the blender timer system
"""
if bpy.app.timers.is_registered(self.main):
logging.info(f"Unregistering {self.id}")
bpy.app.timers.unregister(self.main)
del this.registry[self.id]
self.is_running = False
class SessionRecordGraphTimer(Timer):
def __init__(self, timout=10, filepath=None):
class SessionBackupTimer(Timer):
def __init__(self, timeout=10, filepath=None):
self._filepath = filepath
super().__init__(timout)
super().__init__(timeout)
def execute(self):
operators.dump_db(self._filepath)
class ApplyTimer(Timer):
def __init__(self, timout=1, target_type=None):
def __init__(self, timeout=1, target_type=None):
self._type = target_type
super().__init__(timout)
super().__init__(timeout)
self.id = target_type.__name__
def execute(self):
if session and session.state['STATE'] == STATE_ACTIVE:
@ -148,8 +131,8 @@ class ApplyTimer(Timer):
session.apply(n, force=True)
class DynamicRightSelectTimer(Timer):
def __init__(self, timout=.1):
super().__init__(timout)
def __init__(self, timeout=.1):
super().__init__(timeout)
self._last_selection = []
self._user = None
self._annotating = False
@ -270,8 +253,8 @@ class DynamicRightSelectTimer(Timer):
class ClientUpdate(Timer):
def __init__(self, timout=.1):
super().__init__(timout)
def __init__(self, timeout=.1):
super().__init__(timeout)
self.handle_quit = False
self.users_metadata = {}
@ -333,16 +316,16 @@ class ClientUpdate(Timer):
class SessionStatusUpdate(Timer):
def __init__(self, timout=1):
super().__init__(timout)
def __init__(self, timeout=1):
super().__init__(timeout)
def execute(self):
refresh_sidebar_view()
class SessionUserSync(Timer):
def __init__(self, timout=1):
super().__init__(timout)
def __init__(self, timeout=1):
super().__init__(timeout)
self.settings = utils.get_preferences()
def execute(self):
@ -375,8 +358,8 @@ class SessionUserSync(Timer):
class MainThreadExecutor(Timer):
def __init__(self, timout=1, execution_queue=None):
super().__init__(timout)
def __init__(self, timeout=1, execution_queue=None):
super().__init__(timeout)
self.execution_queue = execution_queue
def execute(self):

View File

@ -29,6 +29,7 @@ from replication.constants import (ADDED, ERROR, FETCHED,
STATE_LAUNCHING_SERVICES)
from replication import __version__
from replication.interface import session
from .timers import registry
ICONS_PROP_STATES = ['TRIA_DOWN', # ADDED
'TRIA_UP', # COMMITED
@ -550,7 +551,6 @@ class SESSION_PT_repository(bpy.types.Panel):
def draw_header(self, context):
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
self.layout.operator('session.export',text="", icon="EXPORT")
def draw(self, context):
layout = self.layout
@ -564,6 +564,13 @@ class SESSION_PT_repository(bpy.types.Panel):
row = layout.row()
if session.state['STATE'] == STATE_ACTIVE:
if 'SessionBackupTimer' in registry:
row.alert = True
row.operator('session.cancel_autosave', icon="CANCEL")
row.alert = False
else:
row.operator('session.save', icon="FILE_TICK")
flow = layout.grid_flow(
row_major=True,
columns=0,