1+ using System . Collections . Generic ;
2+ using System . Diagnostics ;
3+ using System . Threading . Tasks ;
4+ using Codice . Client . BaseCommands ;
5+ using Newtonsoft . Json ;
6+ using Newtonsoft . Json . Linq ;
7+ using Rivet . Editor . UI . TaskPanel ;
8+ using UnityEditor ;
9+
10+ namespace Rivet . Editor
11+ {
12+ public struct TaskConfig
13+ {
14+ public string Name ;
15+ public JObject Input ;
16+ }
17+
18+
19+ /// <summary>
20+ /// Persistent task that can be stopped and restarted with logs.
21+ /// </summary>
22+ public class TaskManager
23+ {
24+ const int MAX_LOG_LEN = 10_000 ;
25+
26+ public enum LogType { META , STDOUT , STDERR }
27+
28+ public struct LogEntry
29+ {
30+ public string Message ;
31+ public LogType Type ;
32+
33+ public LogEntry ( string message , LogType type )
34+ {
35+ Message = message ;
36+ Type = type ;
37+ }
38+ }
39+
40+ public delegate Task < TaskConfig ? > GetTaskConfigDelegate ( ) ;
41+ public delegate TaskPanelWindow ? GetTaskPanelDelegate ( ) ;
42+ public delegate void StateChangeHandler ( bool running ) ;
43+
44+ // Config
45+ private GetTaskConfigDelegate _getTaskConfig ;
46+ private GetTaskPanelDelegate _getTaskPanel ;
47+ private bool _autoRestart = false ;
48+ private bool _taskStopping = false ;
49+
50+ // Events
51+ public event StateChangeHandler StateChange ;
52+
53+ // State
54+ private readonly object _taskLock = new ( ) ;
55+ private RivetTask _task ;
56+ public List < LogEntry > LogEntries = new ( ) ;
57+
58+ public TaskManager ( string initMessage , GetTaskConfigDelegate getTaskConfig , GetTaskPanelDelegate getTaskPanel , bool autoRestart = false )
59+ {
60+ _getTaskConfig = getTaskConfig ;
61+ _getTaskPanel = getTaskPanel ;
62+ _autoRestart = autoRestart ;
63+
64+ LogEntries . Add ( new LogEntry ( initMessage , LogType . META ) ) ;
65+ }
66+
67+ public async Task StartTask ( bool restart = true )
68+ {
69+ lock ( _taskLock )
70+ {
71+ // Do nothing if already stopping another task
72+ if ( _taskStopping )
73+ return ;
74+
75+ // Do nothing if task already running
76+ if ( ! restart && _task != null )
77+ return ;
78+ }
79+
80+ // Kill old task
81+ StopTask ( ) ;
82+
83+ // Start task
84+ var config = await _getTaskConfig ( ) ;
85+ if ( config == null )
86+ {
87+ RivetLogger . Log ( "No task config provided." ) ;
88+ }
89+ lock ( _taskLock )
90+ {
91+ _task = new RivetTask ( config . Value . Name , config . Value . Input ) ;
92+ _taskStopping = false ;
93+ _task . OnLog += OnTaskLog ;
94+ OnStateChange ( ) ;
95+ }
96+
97+ AddLogLine ( "Start" , LogType . META ) ;
98+
99+ // Run task
100+ var output = await _task . RunAsync ( ) ;
101+ lock ( _taskLock )
102+ {
103+ _task = null ;
104+ OnStateChange ( ) ;
105+ }
106+
107+ // Log output
108+ switch ( output )
109+ {
110+ case ResultOk < JObject > ok :
111+ AddLogLine ( $ "Exited with { ok . Data . ToString ( Formatting . None ) } ", LogType . META ) ;
112+ break ;
113+ case ResultErr < JObject > err :
114+ AddLogLine ( $ "Task error: { err . Message } ", LogType . META ) ;
115+ break ;
116+ }
117+
118+ // TODO: Re-enable task restarting
119+ // // Restart if task was not stopped
120+ // bool shouldRestart;
121+ // lock (_taskLock) shouldRestart = _autoRestart && _task == null && !_taskStopping;
122+ // if (shouldRestart)
123+ // {
124+ // AddLogLine("Restarting in 2 seconds", LogType.META);
125+ // await Task.Delay(2000);
126+ // await StartTask();
127+ // }
128+ }
129+
130+ public void StopTask ( )
131+ {
132+ lock ( _taskLock )
133+ {
134+ if ( _task != null )
135+ {
136+ _taskStopping = true ;
137+ _task . Kill ( ) ;
138+ _task = null ;
139+
140+ AddLogLine ( "Stop" , LogType . META ) ;
141+
142+ OnStateChange ( ) ;
143+ }
144+ }
145+ }
146+
147+ private void OnTaskLog ( string message , RivetTask . LogType type )
148+ {
149+ LogType logType = type switch
150+ {
151+ RivetTask . LogType . STDOUT => LogType . STDOUT ,
152+ RivetTask . LogType . STDERR => LogType . STDERR ,
153+ _ => LogType . META
154+ } ;
155+ AddLogLine ( message , logType ) ;
156+ }
157+
158+ public void AddLogLine ( string message , LogType type )
159+ {
160+ LogEntries . Add ( new LogEntry ( message , type ) ) ;
161+ if ( LogEntries . Count > MAX_LOG_LEN )
162+ {
163+ LogEntries . RemoveRange ( 0 , LogEntries . Count - MAX_LOG_LEN ) ;
164+ }
165+
166+ // This might not be called on the main thread
167+ EditorApplication . delayCall += ( ) => _getTaskPanel ( ) ? . UpdateLogs ( ) ;
168+ }
169+
170+ public void ClearLogs ( )
171+ {
172+ LogEntries . Clear ( ) ;
173+ _getTaskPanel ( ) ? . UpdateLogs ( ) ;
174+ }
175+
176+ private void OnStateChange ( )
177+ {
178+ // HACK: Ignore _task.IsRunning because we can't hook in to the
179+ // event right after the task is started (where IsRunning is set to
180+ // true). To fix this, we need to be able to hook in to state change
181+ // events on RivetTask.
182+ StateChange . Invoke ( _task != null ) ;
183+ }
184+ }
185+ }
0 commit comments