Implement thread-safe ChatBot API (#1510, #1579)

+ Rework task scheduling in chatbots
+ Switch back terrain processing to tasks
This commit is contained in:
ORelio 2021-05-15 17:36:16 +02:00
parent c1cfaf520d
commit 95d6318350
7 changed files with 517 additions and 461 deletions

View file

@ -42,7 +42,7 @@ namespace MinecraftClient
private List<string> registeredPluginChannels = new List<String>();
private List<string> registeredCommands = new List<string>();
private object delayTasksLock = new object();
private List<DelayedTask> delayTasks = new List<DelayedTask>();
private List<TaskWithDelay> delayedTasks = new List<TaskWithDelay>();
private McClient Handler
{
get
@ -65,14 +65,14 @@ namespace MinecraftClient
{
lock (delayTasksLock)
{
if (delayTasks.Count > 0)
if (delayedTasks.Count > 0)
{
List<int> tasksToRemove = new List<int>();
for (int i = 0; i < delayTasks.Count; i++)
for (int i = 0; i < delayedTasks.Count; i++)
{
if (delayTasks[i].Tick())
if (delayedTasks[i].Tick())
{
delayTasks[i].Task.DynamicInvoke();
delayedTasks[i].Task();
tasksToRemove.Add(i);
}
}
@ -81,7 +81,7 @@ namespace MinecraftClient
tasksToRemove.Sort((a, b) => b.CompareTo(a)); // descending sort
foreach (int index in tasksToRemove)
{
delayTasks.RemoveAt(index);
delayedTasks.RemoveAt(index);
}
}
}
@ -1390,46 +1390,61 @@ namespace MinecraftClient
}
/// <summary>
/// Schedule a task to run on main thread. Returned value cannot be retrieved
/// Invoke a task on the main thread, wait for completion and retrieve return value.
/// </summary>
/// <param name="task">Task to run with any type or return value</param>
/// <returns>Any result returned from task, result type is inferred from the task</returns>
/// <example>bool result = InvokeOnMainThread(methodThatReturnsAbool);</example>
/// <example>bool result = InvokeOnMainThread(() => methodThatReturnsAbool(argument));</example>
/// <example>int result = InvokeOnMainThread(() => { yourCode(); return 42; });</example>
/// <typeparam name="T">Type of the return value</typeparam>
protected T InvokeOnMainThread<T>(Func<T> task)
{
return Handler.InvokeOnMainThread(task);
}
/// <summary>
/// Invoke a task on the main thread and wait for completion
/// </summary>
/// <param name="task">Task to run without return value</param>
/// <example>InvokeOnMainThread(methodThatReturnsNothing);</example>
/// <example>InvokeOnMainThread(() => methodThatReturnsNothing(argument));</example>
/// <example>InvokeOnMainThread(() => { yourCode(); });</example>
protected void InvokeOnMainThread(Action task)
{
Handler.InvokeOnMainThread(task);
}
/// <summary>
/// Schedule a task to run on the main thread, and do not wait for completion
/// </summary>
/// <param name="task">Task to run</param>
/// <param name="delayTicks">Run the task after X ticks (1 tick delay = ~100ms). 0 for no delay</param>
/// <example>
/// // Delay ~10 seconds
/// ScheduleTaskDelayed(new Action(() =>
/// {
/// /** Your code here **/
/// Console.WriteLine("10 seconds has passed");
/// }), 100);
/// <example>InvokeOnMainThread(methodThatReturnsNothing, 10);</example>
/// <example>InvokeOnMainThread(() => methodThatReturnsNothing(argument), 10);</example>
/// <example>InvokeOnMainThread(() => { yourCode(); }, 10);</example>
/// </example>
// TODO: Adapt to new IMinecraftComHandler API
/*protected void ScheduleTaskDelayed(Delegate task, int delayTicks = 0)
protected void ScheduleOnMainThread(Action task, int delayTicks = 0)
{
if (delayTicks <= 0)
lock (delayTasksLock)
{
// Immediately schedule to run on next update
Handler.InvokeOnMainThread(task);
delayedTasks.Add(new TaskWithDelay(task, delayTicks));
}
else
{
lock (delayTasksLock)
{
delayTasks.Add(new DelayedTask(task, delayTicks));
}
}
}*/
}
/// <summary>
/// Schedule a task to run on main thread.
/// Schedule a task to run on the main thread, and do not wait for completion
/// </summary>
/// <param name="task">Task to run</param>
/// <returns>Any value returned from the task</returns>
// TODO: Adapt to new IMinecraftComHandler API
/*
protected object ScheduleTask(Delegate task)
/// <param name="delay">Run the task after the specified delay</param>
protected void ScheduleOnMainThread(Action task, TimeSpan delay)
{
return Handler.InvokeOnMainThread(task);
}*/
lock (delayTasksLock)
{
delayedTasks.Add(new TaskWithDelay(task, delay));
}
}
/// <summary>
/// Command runner definition.
@ -1475,35 +1490,5 @@ namespace MinecraftClient
this.Runner = callback;
}
}
private class DelayedTask
{
private Delegate task;
private int Counter;
public Delegate Task { get { return task; } }
public DelayedTask(Delegate task)
: this(task, 0)
{ }
public DelayedTask(Delegate task, int delayTicks)
{
this.task = task;
Counter = delayTicks;
}
/// <summary>
/// Tick the counter
/// </summary>
/// <returns>Return true if counted to zero</returns>
public bool Tick()
{
Counter--;
if (Counter <= 0)
return true;
return false;
}
}
}
}