@@ -50,6 +50,12 @@ public class EphysLinkService
5050
5151 #endregion
5252
53+ #region Demo Loop
54+
55+ private readonly Dictionary < string , bool > _runningDemoLoops = new ( ) ;
56+ private const float DEMO_SPEED = 100f ; // µm/s
57+ #endregion
58+
5359 public EphysLinkService ( StoreService storeService )
5460 {
5561 // Register services.
@@ -69,6 +75,25 @@ private async void OnSceneStateChanged(SceneState sceneState)
6975 // Apply small delay to prevent overrunning updates (delay for roughly 60 FPS).
7076 await Task . Delay ( 10 ) ;
7177
78+ // Handle demo loop state changes.
79+ foreach ( var manipulatorState in sceneState . Manipulators )
80+ {
81+ _runningDemoLoops . TryGetValue ( manipulatorState . Id , out var isRunning ) ;
82+
83+ switch ( manipulatorState . IsDemoRunning )
84+ {
85+ // Start demo loop if requested and not already running.
86+ case true when ! isRunning :
87+ _runningDemoLoops [ manipulatorState . Id ] = true ;
88+ _ = RunDemoLoop ( manipulatorState . Id ) ;
89+ break ;
90+ // Stop demo loop if requested and currently running.
91+ case false when isRunning :
92+ await Stop ( manipulatorState . Id ) ;
93+ break ;
94+ }
95+ }
96+
7297 // WARNING: this will create an infinite loop of state updates on purpose.
7398 // Update the position of visualization probes.
7499 await UpdateVisualizationProbePosition ( sceneState ) ;
@@ -669,13 +694,13 @@ public async Task SetManipulatorDuraOffsetToCurrentDepth(string manipulatorId)
669694 var visualizationProbeState = currentSceneState . Probes . FirstOrDefault ( state =>
670695 state . Name == currentSceneState . ActiveManipulatorState . VisualizationProbeName
671696 ) ;
672-
697+
673698 Debug . Log ( "Collecting info" ) ;
674-
699+
675700 // Exit if we cannot find the visualization probe manager or state.
676701 if ( visualizationProbeManager == null || visualizationProbeState == null )
677702 return ;
678-
703+
679704 Debug . Log ( "Found viz states and manager" ) ;
680705
681706 if ( visualizationProbeManager . IsProbeInBrain ( ) )
@@ -693,7 +718,7 @@ public async Task SetManipulatorDuraOffsetToCurrentDepth(string manipulatorId)
693718 // We need to calculate the surface coordinate ourselves
694719 var ( brainSurfaceCoordinateIdx , _) =
695720 visualizationProbeManager . CalculateEntryCoordinate ( ) ;
696-
721+
697722 // Exit if not in brain.
698723 if ( float . IsNaN ( brainSurfaceCoordinateIdx . x ) )
699724 return ;
@@ -716,6 +741,177 @@ public async Task SetManipulatorDuraOffsetToCurrentDepth(string manipulatorId)
716741 ) ;
717742 }
718743
744+ public async Task SetManipulatorDemoHomeCoordinateToCurrentPosition ( string manipulatorId )
745+ {
746+ var currentPositionResponse = await GetPosition ( manipulatorId ) ;
747+ if ( HasError ( currentPositionResponse . Error ) )
748+ return ;
749+ _storeService . Store . Dispatch (
750+ SceneActions . SET_MANIPULATOR_DEMO_HOME_COORDINATE ,
751+ ( manipulatorId , currentPositionResponse . Position )
752+ ) ;
753+ }
754+
755+ public async Task SetManipulatorDemoTargetCoordinateToCurrentPosition ( string manipulatorId )
756+ {
757+ var currentPositionResponse = await GetPosition ( manipulatorId ) ;
758+ if ( HasError ( currentPositionResponse . Error ) )
759+ return ;
760+ _storeService . Store . Dispatch (
761+ SceneActions . SET_MANIPULATOR_DEMO_TARGET_COORDINATE ,
762+ ( manipulatorId , currentPositionResponse . Position )
763+ ) ;
764+ }
765+
766+ /// <summary>
767+ /// Run the demo loop for a manipulator.
768+ /// Sequence: Home -> Target (X,Z only) -> Target (Y,W only) -> Back to Target (X,Z only) -> repeat indefinitely
769+ /// until IsDemoRunning is set to false or an error occurs.
770+ /// </summary>
771+ /// <param name="manipulatorId">ID of the manipulator to run demo loop for.</param>
772+ private async Task RunDemoLoop ( string manipulatorId )
773+ {
774+ while ( true )
775+ {
776+ // Get current manipulator state.
777+ var sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
778+ var manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
779+ m . Id == manipulatorId
780+ ) ;
781+
782+ // Exit if manipulator not found.
783+ if ( manipulatorState == null )
784+ {
785+ _runningDemoLoops [ manipulatorId ] = false ;
786+ return ;
787+ }
788+
789+ // Exit if demo is no longer running.
790+ if ( ! manipulatorState . IsDemoRunning )
791+ {
792+ _runningDemoLoops [ manipulatorId ] = false ;
793+ return ;
794+ }
795+
796+ // Step 1: Move to home position.
797+ var homeRequest = new SetPositionRequest (
798+ manipulatorId ,
799+ manipulatorState . DemoHomeCoordinate ,
800+ DEMO_SPEED
801+ ) ;
802+ var homeResponse = await SetPosition ( homeRequest ) ;
803+
804+ // Exit on error (including stop request).
805+ if ( HasError ( homeResponse . Error ) )
806+ {
807+ _runningDemoLoops [ manipulatorId ] = false ;
808+ return ;
809+ }
810+
811+ // Refresh state and check if demo is still running.
812+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
813+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
814+ m . Id == manipulatorId
815+ ) ;
816+ if ( manipulatorState is not { IsDemoRunning : true } )
817+ {
818+ _runningDemoLoops [ manipulatorId ] = false ;
819+ return ;
820+ }
821+
822+ // Step 2: Move to target position on X and Z axes only (keep Y and W from home).
823+ var intermediatePosition = new Vector4 (
824+ manipulatorState . DemoTargetCoordinate . x ,
825+ manipulatorState . DemoHomeCoordinate . y ,
826+ manipulatorState . DemoTargetCoordinate . z ,
827+ manipulatorState . DemoHomeCoordinate . w
828+ ) ;
829+ var intermediateRequest = new SetPositionRequest (
830+ manipulatorId ,
831+ intermediatePosition ,
832+ DEMO_SPEED
833+ ) ;
834+ var intermediateResponse = await SetPosition ( intermediateRequest ) ;
835+
836+ // Exit on error (including stop request).
837+ if ( HasError ( intermediateResponse . Error ) )
838+ {
839+ _runningDemoLoops [ manipulatorId ] = false ;
840+ return ;
841+ }
842+
843+ // Refresh state and check if demo is still running.
844+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
845+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
846+ m . Id == manipulatorId
847+ ) ;
848+ if ( manipulatorState is not { IsDemoRunning : true } )
849+ {
850+ _runningDemoLoops [ manipulatorId ] = false ;
851+ return ;
852+ }
853+
854+ // Step 3: Move to target position on Y and W axes only (complete movement to target).
855+ var targetRequest = new SetPositionRequest (
856+ manipulatorId ,
857+ manipulatorState . DemoTargetCoordinate ,
858+ DEMO_SPEED
859+ ) ;
860+ var targetResponse = await SetPosition ( targetRequest ) ;
861+
862+ // Exit on error (including stop request).
863+ if ( HasError ( targetResponse . Error ) )
864+ {
865+ _runningDemoLoops [ manipulatorId ] = false ;
866+ return ;
867+ }
868+
869+ // Refresh state and check if demo is still running.
870+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
871+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
872+ m . Id == manipulatorId
873+ ) ;
874+ if ( manipulatorState is not { IsDemoRunning : true } )
875+ {
876+ _runningDemoLoops [ manipulatorId ] = false ;
877+ return ;
878+ }
879+
880+ // Step 4: Move back to intermediate position (X,Z only, keeping Y,W from home).
881+ var returnToIntermediatePosition = new Vector4 (
882+ manipulatorState . DemoTargetCoordinate . x ,
883+ manipulatorState . DemoHomeCoordinate . y ,
884+ manipulatorState . DemoTargetCoordinate . z ,
885+ manipulatorState . DemoHomeCoordinate . w
886+ ) ;
887+ var returnToIntermediateRequest = new SetPositionRequest (
888+ manipulatorId ,
889+ returnToIntermediatePosition ,
890+ DEMO_SPEED
891+ ) ;
892+ var returnToIntermediateResponse = await SetPosition ( returnToIntermediateRequest ) ;
893+
894+ // Exit on error (including stop request).
895+ if ( HasError ( returnToIntermediateResponse . Error ) )
896+ {
897+ _runningDemoLoops [ manipulatorId ] = false ;
898+ return ;
899+ }
900+
901+ // Refresh state and check if demo is still running before next iteration.
902+ sceneState = _storeService . Store . GetState < SceneState > ( SliceNames . SCENE_SLICE ) ;
903+ manipulatorState = sceneState . Manipulators . FirstOrDefault ( m =>
904+ m . Id == manipulatorId
905+ ) ;
906+ if ( manipulatorState is { IsDemoRunning : true } )
907+ {
908+ // Loop continues to next iteration (back to home).
909+ continue ;
910+ }
911+ _runningDemoLoops [ manipulatorId ] = false ;
912+ return ;
913+ }
914+
719915 #endregion
720916 }
721917}
0 commit comments