clean: timers interface
feat: cancel autosave operator
This commit is contained in:
@ -42,21 +42,20 @@ import bpy
|
|||||||
import mathutils
|
import mathutils
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||||
from replication.constants import (FETCHED, RP_COMMON, STATE_ACTIVE,
|
from replication.constants import (COMMITED, FETCHED, RP_COMMON, STATE_ACTIVE,
|
||||||
STATE_INITIAL, STATE_SYNCING, UP,
|
STATE_INITIAL, STATE_SYNCING, UP)
|
||||||
COMMITED)
|
|
||||||
from replication.data import ReplicatedDataFactory
|
from replication.data import ReplicatedDataFactory
|
||||||
from replication.exception import NonAuthorizedOperationError
|
from replication.exception import NonAuthorizedOperationError
|
||||||
from replication.interface import session
|
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 .presence import SessionStatusWidget, renderer, view3d_find
|
||||||
|
from .timers import registry
|
||||||
|
|
||||||
background_execution_queue = Queue()
|
background_execution_queue = Queue()
|
||||||
deleyables = []
|
deleyables = []
|
||||||
stop_modal_executor = False
|
stop_modal_executor = False
|
||||||
|
|
||||||
|
|
||||||
def session_callback(name):
|
def session_callback(name):
|
||||||
""" Session callback wrapper
|
""" Session callback wrapper
|
||||||
|
|
||||||
@ -204,8 +203,8 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
if settings.update_method == 'DEFAULT':
|
if settings.update_method == 'DEFAULT':
|
||||||
if type_local_config.bl_delay_apply > 0:
|
if type_local_config.bl_delay_apply > 0:
|
||||||
deleyables.append(
|
deleyables.append(
|
||||||
delayable.ApplyTimer(
|
timers.ApplyTimer(
|
||||||
timout=type_local_config.bl_delay_apply,
|
timeout=type_local_config.bl_delay_apply,
|
||||||
target_type=type_module_class))
|
target_type=type_module_class))
|
||||||
|
|
||||||
if bpy.app.version[1] >= 91:
|
if bpy.app.version[1] >= 91:
|
||||||
@ -219,7 +218,7 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
external_update_handling=use_extern_update)
|
external_update_handling=use_extern_update)
|
||||||
|
|
||||||
if settings.update_method == 'DEPSGRAPH':
|
if settings.update_method == 'DEPSGRAPH':
|
||||||
deleyables.append(delayable.ApplyTimer(
|
deleyables.append(timers.ApplyTimer(
|
||||||
settings.depsgraph_update_rate/1000))
|
settings.depsgraph_update_rate/1000))
|
||||||
|
|
||||||
# Host a session
|
# Host a session
|
||||||
@ -270,12 +269,12 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
logging.error(str(e))
|
logging.error(str(e))
|
||||||
|
|
||||||
# Background client updates service
|
# Background client updates service
|
||||||
deleyables.append(delayable.ClientUpdate())
|
deleyables.append(timers.ClientUpdate())
|
||||||
deleyables.append(delayable.DynamicRightSelectTimer())
|
deleyables.append(timers.DynamicRightSelectTimer())
|
||||||
|
|
||||||
session_update = delayable.SessionStatusUpdate()
|
session_update = timers.SessionStatusUpdate()
|
||||||
session_user_sync = delayable.SessionUserSync()
|
session_user_sync = timers.SessionUserSync()
|
||||||
session_background_executor = delayable.MainThreadExecutor(
|
session_background_executor = timers.MainThreadExecutor(
|
||||||
execution_queue=background_execution_queue)
|
execution_queue=background_execution_queue)
|
||||||
|
|
||||||
session_update.register()
|
session_update.register()
|
||||||
@ -750,31 +749,65 @@ def dump_db(filepath):
|
|||||||
filepath = Path(filepath)
|
filepath = Path(filepath)
|
||||||
filepath = filepath.with_name(f"{filepath.stem}_{stime}{filepath.suffix}")
|
filepath = filepath.with_name(f"{filepath.stem}_{stime}{filepath.suffix}")
|
||||||
with gzip.open(filepath, "wb") as f:
|
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)
|
pickle.dump(db, f, protocol=4)
|
||||||
|
|
||||||
|
|
||||||
class SessionRecordGraphOperator(bpy.types.Operator, ExportHelper):
|
class SessionSaveBackupOperator(bpy.types.Operator, ExportHelper):
|
||||||
bl_idname = "session.export"
|
bl_idname = "session.save"
|
||||||
bl_label = "SessionRecordGraph"
|
bl_label = "Save session"
|
||||||
|
bl_description = "Save a snapshot of the collaborative session"
|
||||||
|
|
||||||
# ExportHelper mixin class uses this
|
# ExportHelper mixin class uses this
|
||||||
filename_ext = ".db"
|
filename_ext = ".db"
|
||||||
|
|
||||||
def execute(self, context):
|
enable_autosave: bpy.props.BoolProperty(
|
||||||
recorder = delayable.SessionRecordGraphTimer(filepath=self.filepath)
|
name="Auto-save",
|
||||||
recorder.register()
|
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)
|
||||||
|
|
||||||
deleyables.append(recorder)
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return session.state['STATE'] == STATE_ACTIVE
|
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):
|
class SessionLoadGraphOperator(bpy.types.Operator, ImportHelper):
|
||||||
bl_idname = "session.load"
|
bl_idname = "session.load"
|
||||||
bl_label = "SessionLoadGraph"
|
bl_label = "SessionLoadGraph"
|
||||||
|
bl_description = "Load a Multi-user session save"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
# ExportHelper mixin class uses this
|
# ExportHelper mixin class uses this
|
||||||
@ -782,6 +815,7 @@ class SessionLoadGraphOperator(bpy.types.Operator, ImportHelper):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
from replication.graph import ReplicationGraph
|
from replication.graph import ReplicationGraph
|
||||||
|
|
||||||
# TODO: add filechecks
|
# TODO: add filechecks
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -865,8 +899,9 @@ classes = (
|
|||||||
SessionInitOperator,
|
SessionInitOperator,
|
||||||
SessionClearCache,
|
SessionClearCache,
|
||||||
SessionNotifyOperator,
|
SessionNotifyOperator,
|
||||||
SessionRecordGraphOperator,
|
SessionSaveBackupOperator,
|
||||||
SessionLoadGraphOperator,
|
SessionLoadGraphOperator,
|
||||||
|
SessionStopAutoSaveOperator,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,68 +16,48 @@
|
|||||||
# ##### END GPL LICENSE BLOCK #####
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
from replication.constants import (FETCHED, RP_COMMON, STATE_ACTIVE,
|
||||||
from . import utils
|
STATE_INITIAL, STATE_LOBBY, STATE_QUITTING,
|
||||||
from .presence import (renderer,
|
STATE_SRV_SYNC, STATE_SYNCING, UP)
|
||||||
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.exception import NonAuthorizedOperationError
|
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):
|
def is_annotating(context: bpy.types.Context):
|
||||||
""" Check if the annotate mode is enabled
|
""" Check if the annotate mode is enabled
|
||||||
"""
|
"""
|
||||||
return bpy.context.workspace.tools.from_space_view3d_mode('OBJECT', create=False).idname == 'builtin.annotate'
|
return bpy.context.workspace.tools.from_space_view3d_mode('OBJECT', create=False).idname == 'builtin.annotate'
|
||||||
|
|
||||||
class Delayable():
|
|
||||||
"""Delayable task interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
def register(self):
|
class Timer(object):
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def unregister(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class Timer(Delayable):
|
|
||||||
"""Timer binder interface for blender
|
"""Timer binder interface for blender
|
||||||
|
|
||||||
Run a bpy.app.Timer in the background looping at the given rate
|
Run a bpy.app.Timer in the background looping at the given rate
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, duration=1):
|
def __init__(self, timeout=10, id=None):
|
||||||
super().__init__()
|
self._timeout = timeout
|
||||||
self._timeout = duration
|
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
self.id = id if id else self.__class__.__name__
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
"""Register the timer into the blender timer system
|
"""Register the timer into the blender timer system
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.is_running:
|
if not self.is_running:
|
||||||
|
this.registry[self.id] = self
|
||||||
bpy.app.timers.register(self.main)
|
bpy.app.timers.register(self.main)
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
logging.debug(f"Register {self.__class__.__name__}")
|
logging.debug(f"Register {self.__class__.__name__}")
|
||||||
@ -105,23 +85,26 @@ class Timer(Delayable):
|
|||||||
"""Unnegister the timer of the blender timer system
|
"""Unnegister the timer of the blender timer system
|
||||||
"""
|
"""
|
||||||
if bpy.app.timers.is_registered(self.main):
|
if bpy.app.timers.is_registered(self.main):
|
||||||
|
logging.info(f"Unregistering {self.id}")
|
||||||
bpy.app.timers.unregister(self.main)
|
bpy.app.timers.unregister(self.main)
|
||||||
|
|
||||||
|
del this.registry[self.id]
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
class SessionRecordGraphTimer(Timer):
|
class SessionBackupTimer(Timer):
|
||||||
def __init__(self, timout=10, filepath=None):
|
def __init__(self, timeout=10, filepath=None):
|
||||||
self._filepath = filepath
|
self._filepath = filepath
|
||||||
super().__init__(timout)
|
super().__init__(timeout)
|
||||||
|
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
operators.dump_db(self._filepath)
|
operators.dump_db(self._filepath)
|
||||||
|
|
||||||
class ApplyTimer(Timer):
|
class ApplyTimer(Timer):
|
||||||
def __init__(self, timout=1, target_type=None):
|
def __init__(self, timeout=1, target_type=None):
|
||||||
self._type = target_type
|
self._type = target_type
|
||||||
super().__init__(timout)
|
super().__init__(timeout)
|
||||||
|
self.id = target_type.__name__
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
if session and session.state['STATE'] == STATE_ACTIVE:
|
if session and session.state['STATE'] == STATE_ACTIVE:
|
||||||
@ -148,8 +131,8 @@ class ApplyTimer(Timer):
|
|||||||
session.apply(n, force=True)
|
session.apply(n, force=True)
|
||||||
|
|
||||||
class DynamicRightSelectTimer(Timer):
|
class DynamicRightSelectTimer(Timer):
|
||||||
def __init__(self, timout=.1):
|
def __init__(self, timeout=.1):
|
||||||
super().__init__(timout)
|
super().__init__(timeout)
|
||||||
self._last_selection = []
|
self._last_selection = []
|
||||||
self._user = None
|
self._user = None
|
||||||
self._annotating = False
|
self._annotating = False
|
||||||
@ -270,8 +253,8 @@ class DynamicRightSelectTimer(Timer):
|
|||||||
|
|
||||||
|
|
||||||
class ClientUpdate(Timer):
|
class ClientUpdate(Timer):
|
||||||
def __init__(self, timout=.1):
|
def __init__(self, timeout=.1):
|
||||||
super().__init__(timout)
|
super().__init__(timeout)
|
||||||
self.handle_quit = False
|
self.handle_quit = False
|
||||||
self.users_metadata = {}
|
self.users_metadata = {}
|
||||||
|
|
||||||
@ -333,16 +316,16 @@ class ClientUpdate(Timer):
|
|||||||
|
|
||||||
|
|
||||||
class SessionStatusUpdate(Timer):
|
class SessionStatusUpdate(Timer):
|
||||||
def __init__(self, timout=1):
|
def __init__(self, timeout=1):
|
||||||
super().__init__(timout)
|
super().__init__(timeout)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
refresh_sidebar_view()
|
refresh_sidebar_view()
|
||||||
|
|
||||||
|
|
||||||
class SessionUserSync(Timer):
|
class SessionUserSync(Timer):
|
||||||
def __init__(self, timout=1):
|
def __init__(self, timeout=1):
|
||||||
super().__init__(timout)
|
super().__init__(timeout)
|
||||||
self.settings = utils.get_preferences()
|
self.settings = utils.get_preferences()
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
@ -375,8 +358,8 @@ class SessionUserSync(Timer):
|
|||||||
|
|
||||||
|
|
||||||
class MainThreadExecutor(Timer):
|
class MainThreadExecutor(Timer):
|
||||||
def __init__(self, timout=1, execution_queue=None):
|
def __init__(self, timeout=1, execution_queue=None):
|
||||||
super().__init__(timout)
|
super().__init__(timeout)
|
||||||
self.execution_queue = execution_queue
|
self.execution_queue = execution_queue
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
@ -29,6 +29,7 @@ from replication.constants import (ADDED, ERROR, FETCHED,
|
|||||||
STATE_LAUNCHING_SERVICES)
|
STATE_LAUNCHING_SERVICES)
|
||||||
from replication import __version__
|
from replication import __version__
|
||||||
from replication.interface import session
|
from replication.interface import session
|
||||||
|
from .timers import registry
|
||||||
|
|
||||||
ICONS_PROP_STATES = ['TRIA_DOWN', # ADDED
|
ICONS_PROP_STATES = ['TRIA_DOWN', # ADDED
|
||||||
'TRIA_UP', # COMMITED
|
'TRIA_UP', # COMMITED
|
||||||
@ -550,7 +551,6 @@ class SESSION_PT_repository(bpy.types.Panel):
|
|||||||
|
|
||||||
def draw_header(self, context):
|
def draw_header(self, context):
|
||||||
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
|
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
|
||||||
self.layout.operator('session.export',text="", icon="EXPORT")
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
@ -564,6 +564,13 @@ class SESSION_PT_repository(bpy.types.Panel):
|
|||||||
row = layout.row()
|
row = layout.row()
|
||||||
|
|
||||||
if session.state['STATE'] == STATE_ACTIVE:
|
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(
|
flow = layout.grid_flow(
|
||||||
row_major=True,
|
row_major=True,
|
||||||
columns=0,
|
columns=0,
|
||||||
|
Reference in New Issue
Block a user