mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Improve InvokeOnMainThread mechanism
Add documentation to make the invoke mechanism easier to understand Make it clear in documentation that code is invoked synchronously Use Action and Func<T> for minimizing the amount of code to write Use type parameter T to automatically adjust return value type Throw exceptions on the calling thread, not the main thread
This commit is contained in:
parent
9e5364a4ff
commit
c1cfaf520d
6 changed files with 164 additions and 59 deletions
|
|
@ -1402,12 +1402,13 @@ namespace MinecraftClient
|
|||
/// Console.WriteLine("10 seconds has passed");
|
||||
/// }), 100);
|
||||
/// </example>
|
||||
protected void ScheduleTaskDelayed(Delegate task, int delayTicks = 0)
|
||||
// TODO: Adapt to new IMinecraftComHandler API
|
||||
/*protected void ScheduleTaskDelayed(Delegate task, int delayTicks = 0)
|
||||
{
|
||||
if (delayTicks <= 0)
|
||||
{
|
||||
// Immediately schedule to run on next update
|
||||
Handler.ScheduleTask(task);
|
||||
Handler.InvokeOnMainThread(task);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1416,17 +1417,19 @@ namespace MinecraftClient
|
|||
delayTasks.Add(new DelayedTask(task, delayTicks));
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a task to run on main thread.
|
||||
/// </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)
|
||||
{
|
||||
return Handler.ScheduleTask(task);
|
||||
}
|
||||
return Handler.InvokeOnMainThread(task);
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// Command runner definition.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace MinecraftClient
|
|||
private Queue<string> chatQueue = new Queue<string>();
|
||||
private static DateTime nextMessageSendTime = DateTime.MinValue;
|
||||
|
||||
private Queue<TaskWithResult> threadTasks = new Queue<TaskWithResult>();
|
||||
private Queue<Action> threadTasks = new Queue<Action>();
|
||||
private object threadTasksLock = new object();
|
||||
|
||||
private readonly List<ChatBot> bots = new List<ChatBot>();
|
||||
|
|
@ -302,7 +302,6 @@ namespace MinecraftClient
|
|||
|
||||
/// <summary>
|
||||
/// Allows the user to send chat messages, commands, and leave the server.
|
||||
/// Enqueue text typed in the command prompt for processing on the main thread.
|
||||
/// </summary>
|
||||
private void CommandPrompt()
|
||||
{
|
||||
|
|
@ -312,7 +311,7 @@ namespace MinecraftClient
|
|||
while (client.Client.Connected)
|
||||
{
|
||||
string text = ConsoleIO.ReadLine();
|
||||
ScheduleTask(new Action(() => { HandleCommandPromptText(text); }));
|
||||
InvokeOnMainThread(() => HandleCommandPromptText(text));
|
||||
}
|
||||
}
|
||||
catch (IOException) { }
|
||||
|
|
@ -641,14 +640,12 @@ namespace MinecraftClient
|
|||
SendRespawnPacket();
|
||||
}
|
||||
|
||||
|
||||
lock (threadTasksLock)
|
||||
{
|
||||
while (threadTasks.Count > 0)
|
||||
{
|
||||
var taskToRun = threadTasks.Dequeue();
|
||||
taskToRun.Execute();
|
||||
taskToRun.Release();
|
||||
Action taskToRun = threadTasks.Dequeue();
|
||||
taskToRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -696,28 +693,43 @@ namespace MinecraftClient
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a task to run on the main thread
|
||||
/// Invoke a task on the main thread, wait for completion and retrieve return value.
|
||||
/// </summary>
|
||||
/// <param name="task">Task to run</param>
|
||||
/// <returns>Any result returned from delegate</returns>
|
||||
public object ScheduleTask(Delegate task)
|
||||
/// <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>
|
||||
public T InvokeOnMainThread<T>(Func<T> task)
|
||||
{
|
||||
if (!InvokeRequired())
|
||||
{
|
||||
return task.DynamicInvoke();
|
||||
return task();
|
||||
}
|
||||
else
|
||||
{
|
||||
var taskAndResult = new TaskWithResult(task);
|
||||
TaskWithResult<T> taskWithResult = new TaskWithResult<T>(task);
|
||||
lock (threadTasksLock)
|
||||
{
|
||||
threadTasks.Enqueue(taskAndResult);
|
||||
threadTasks.Enqueue(taskWithResult.ExecuteSynchronously);
|
||||
}
|
||||
taskAndResult.Block();
|
||||
return taskAndResult.Result;
|
||||
return taskWithResult.WaitGetResult();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public void InvokeOnMainThread(Action task)
|
||||
{
|
||||
InvokeOnMainThread(() => { task(); return true; });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if calling thread is main thread or other thread
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -423,10 +423,9 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
int compressedDataSize = dataTypes.ReadNextInt(packetData);
|
||||
byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData);
|
||||
byte[] decompressed = ZlibUtils.Decompress(compressed);
|
||||
new Task(new Action(() =>
|
||||
{
|
||||
new Thread(() => {
|
||||
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue<byte>(decompressed));
|
||||
})).Start();
|
||||
}).Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -450,10 +449,9 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
else dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above
|
||||
}
|
||||
int dataSize = dataTypes.ReadNextVarInt(packetData);
|
||||
new Task(new Action(() =>
|
||||
{
|
||||
new Thread(() => {
|
||||
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData);
|
||||
})).Start();
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -162,12 +162,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
|
||||
//We have our chunk, save the chunk into the world
|
||||
handler.ScheduleTask(new Action(() =>
|
||||
handler.InvokeOnMainThread(() =>
|
||||
{
|
||||
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
||||
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
||||
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
||||
}));
|
||||
});
|
||||
|
||||
//Pre-1.14 Lighting data
|
||||
if (protocolversion < Protocol18Handler.MC114Version)
|
||||
|
|
@ -192,10 +192,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (chunksContinuous && chunkMask == 0)
|
||||
{
|
||||
//Unload the entire chunk column
|
||||
handler.ScheduleTask(new Action(() =>
|
||||
handler.InvokeOnMainThread(() =>
|
||||
{
|
||||
handler.GetWorld()[chunkX, chunkZ] = null;
|
||||
}));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -214,12 +214,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
chunk[blockX, blockY, blockZ] = new Block(queue.Dequeue());
|
||||
|
||||
//We have our chunk, save the chunk into the world
|
||||
handler.ScheduleTask(new Action(() =>
|
||||
handler.InvokeOnMainThread(() =>
|
||||
{
|
||||
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
||||
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
||||
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,10 +248,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (chunksContinuous && chunkMask == 0)
|
||||
{
|
||||
//Unload the entire chunk column
|
||||
handler.ScheduleTask(new Action(() =>
|
||||
handler.InvokeOnMainThread(() =>
|
||||
{
|
||||
handler.GetWorld()[chunkX, chunkZ] = null;
|
||||
}));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -297,12 +297,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
|
||||
chunk[blockX, blockY, blockZ] = new Block(blockTypes.Dequeue(), blockMeta.Dequeue());
|
||||
|
||||
handler.ScheduleTask(new Action(() =>
|
||||
handler.InvokeOnMainThread(() =>
|
||||
{
|
||||
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
||||
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
||||
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,11 +41,24 @@ namespace MinecraftClient.Protocol
|
|||
ILogger GetLogger();
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a task to run on the main thread
|
||||
/// Invoke a task on the main thread, wait for completion and retrieve return value.
|
||||
/// </summary>
|
||||
/// <param name="task">Task to run</param>
|
||||
/// <returns>Any result returned from delegate</returns>
|
||||
object ScheduleTask(Delegate task);
|
||||
/// <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>
|
||||
T InvokeOnMainThread<T>(Func<T> 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>
|
||||
void InvokeOnMainThread(Action task);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a network packet received or sent
|
||||
|
|
|
|||
|
|
@ -6,42 +6,121 @@ using System.Threading;
|
|||
|
||||
namespace MinecraftClient
|
||||
{
|
||||
public class TaskWithResult
|
||||
/// <summary>
|
||||
/// Holds an asynchronous task with return value
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the return value</typeparam>
|
||||
public class TaskWithResult<T>
|
||||
{
|
||||
private Delegate Task;
|
||||
private AutoResetEvent ResultEvent = new AutoResetEvent(false);
|
||||
private AutoResetEvent resultEvent = new AutoResetEvent(false);
|
||||
private Func<T> task;
|
||||
private T result = default(T);
|
||||
private Exception exception = null;
|
||||
private bool taskRun = false;
|
||||
private object taskRunLock = new object();
|
||||
|
||||
public object Result;
|
||||
|
||||
public TaskWithResult(Delegate task)
|
||||
/// <summary>
|
||||
/// Create a new asynchronous task with return value
|
||||
/// </summary>
|
||||
/// <param name="task">Delegate with return value</param>
|
||||
public TaskWithResult(Func<T> task)
|
||||
{
|
||||
Task = task;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the delegate and set the <see cref="Result"/> property to the returned value
|
||||
/// Check whether the task has finished running
|
||||
/// </summary>
|
||||
/// <returns>Value returned from delegate</returns>
|
||||
public object Execute()
|
||||
public bool HasRun
|
||||
{
|
||||
Result = Task.DynamicInvoke();
|
||||
return Result;
|
||||
get
|
||||
{
|
||||
return taskRun;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Block the program execution
|
||||
/// Get the task result (return value of the inner delegate)
|
||||
/// </summary>
|
||||
public void Block()
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if the task is not finished yet</exception>
|
||||
public T Result
|
||||
{
|
||||
ResultEvent.WaitOne();
|
||||
get
|
||||
{
|
||||
if (taskRun)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else throw new InvalidOperationException("Attempting to retrieve the result of an unfinished task");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume the program execution
|
||||
/// Get the exception thrown by the inner delegate, if any
|
||||
/// </summary>
|
||||
public void Release()
|
||||
public Exception Exception
|
||||
{
|
||||
ResultEvent.Set();
|
||||
get
|
||||
{
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the task in the current thread and set the <see cref="Result"/> property or <see cref=""/>to the returned value
|
||||
/// </summary>
|
||||
public void ExecuteSynchronously()
|
||||
{
|
||||
// Make sur the task will not run twice
|
||||
lock (taskRunLock)
|
||||
{
|
||||
if (taskRun)
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to run a task twice");
|
||||
}
|
||||
}
|
||||
|
||||
// Run the task
|
||||
try
|
||||
{
|
||||
result = task();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exception = e;
|
||||
}
|
||||
|
||||
// Mark task as complete and release wait event
|
||||
lock (taskRunLock)
|
||||
{
|
||||
taskRun = true;
|
||||
}
|
||||
resultEvent.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait until the task has run from another thread and get the returned value or exception thrown by the task
|
||||
/// </summary>
|
||||
/// <returns>Task result once available</returns>
|
||||
/// <exception cref="System.Exception">Any exception thrown by the task</exception>
|
||||
public T WaitGetResult()
|
||||
{
|
||||
// Wait only if the result is not available yet
|
||||
bool mustWait = false;
|
||||
lock (taskRunLock)
|
||||
{
|
||||
mustWait = !taskRun;
|
||||
}
|
||||
if (mustWait)
|
||||
{
|
||||
resultEvent.WaitOne();
|
||||
}
|
||||
|
||||
// Receive exception from task
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue