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");
|
/// Console.WriteLine("10 seconds has passed");
|
||||||
/// }), 100);
|
/// }), 100);
|
||||||
/// </example>
|
/// </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)
|
if (delayTicks <= 0)
|
||||||
{
|
{
|
||||||
// Immediately schedule to run on next update
|
// Immediately schedule to run on next update
|
||||||
Handler.ScheduleTask(task);
|
Handler.InvokeOnMainThread(task);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1416,17 +1417,19 @@ namespace MinecraftClient
|
||||||
delayTasks.Add(new DelayedTask(task, delayTicks));
|
delayTasks.Add(new DelayedTask(task, delayTicks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Schedule a task to run on main thread.
|
/// Schedule a task to run on main thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="task">Task to run</param>
|
/// <param name="task">Task to run</param>
|
||||||
/// <returns>Any value returned from the task</returns>
|
/// <returns>Any value returned from the task</returns>
|
||||||
|
// TODO: Adapt to new IMinecraftComHandler API
|
||||||
|
/*
|
||||||
protected object ScheduleTask(Delegate task)
|
protected object ScheduleTask(Delegate task)
|
||||||
{
|
{
|
||||||
return Handler.ScheduleTask(task);
|
return Handler.InvokeOnMainThread(task);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command runner definition.
|
/// Command runner definition.
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace MinecraftClient
|
||||||
private Queue<string> chatQueue = new Queue<string>();
|
private Queue<string> chatQueue = new Queue<string>();
|
||||||
private static DateTime nextMessageSendTime = DateTime.MinValue;
|
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 object threadTasksLock = new object();
|
||||||
|
|
||||||
private readonly List<ChatBot> bots = new List<ChatBot>();
|
private readonly List<ChatBot> bots = new List<ChatBot>();
|
||||||
|
|
@ -302,7 +302,6 @@ namespace MinecraftClient
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows the user to send chat messages, commands, and leave the server.
|
/// 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>
|
/// </summary>
|
||||||
private void CommandPrompt()
|
private void CommandPrompt()
|
||||||
{
|
{
|
||||||
|
|
@ -312,7 +311,7 @@ namespace MinecraftClient
|
||||||
while (client.Client.Connected)
|
while (client.Client.Connected)
|
||||||
{
|
{
|
||||||
string text = ConsoleIO.ReadLine();
|
string text = ConsoleIO.ReadLine();
|
||||||
ScheduleTask(new Action(() => { HandleCommandPromptText(text); }));
|
InvokeOnMainThread(() => HandleCommandPromptText(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException) { }
|
catch (IOException) { }
|
||||||
|
|
@ -641,14 +640,12 @@ namespace MinecraftClient
|
||||||
SendRespawnPacket();
|
SendRespawnPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
lock (threadTasksLock)
|
lock (threadTasksLock)
|
||||||
{
|
{
|
||||||
while (threadTasks.Count > 0)
|
while (threadTasks.Count > 0)
|
||||||
{
|
{
|
||||||
var taskToRun = threadTasks.Dequeue();
|
Action taskToRun = threadTasks.Dequeue();
|
||||||
taskToRun.Execute();
|
taskToRun();
|
||||||
taskToRun.Release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -696,28 +693,43 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="task">Task to run</param>
|
/// <param name="task">Task to run with any type or return value</param>
|
||||||
/// <returns>Any result returned from delegate</returns>
|
/// <returns>Any result returned from task, result type is inferred from the task</returns>
|
||||||
public object ScheduleTask(Delegate task)
|
/// <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())
|
if (!InvokeRequired())
|
||||||
{
|
{
|
||||||
return task.DynamicInvoke();
|
return task();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var taskAndResult = new TaskWithResult(task);
|
TaskWithResult<T> taskWithResult = new TaskWithResult<T>(task);
|
||||||
lock (threadTasksLock)
|
lock (threadTasksLock)
|
||||||
{
|
{
|
||||||
threadTasks.Enqueue(taskAndResult);
|
threadTasks.Enqueue(taskWithResult.ExecuteSynchronously);
|
||||||
}
|
}
|
||||||
taskAndResult.Block();
|
return taskWithResult.WaitGetResult();
|
||||||
return taskAndResult.Result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Check if calling thread is main thread or other thread
|
/// Check if calling thread is main thread or other thread
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -423,10 +423,9 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
int compressedDataSize = dataTypes.ReadNextInt(packetData);
|
int compressedDataSize = dataTypes.ReadNextInt(packetData);
|
||||||
byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData);
|
byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData);
|
||||||
byte[] decompressed = ZlibUtils.Decompress(compressed);
|
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));
|
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue<byte>(decompressed));
|
||||||
})).Start();
|
}).Start();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -450,10 +449,9 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
else dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above
|
else dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above
|
||||||
}
|
}
|
||||||
int dataSize = dataTypes.ReadNextVarInt(packetData);
|
int dataSize = dataTypes.ReadNextVarInt(packetData);
|
||||||
new Task(new Action(() =>
|
new Thread(() => {
|
||||||
{
|
|
||||||
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData);
|
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData);
|
||||||
})).Start();
|
}).Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -162,12 +162,12 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
//We have our chunk, save the chunk into the world
|
//We have our chunk, save the chunk into the world
|
||||||
handler.ScheduleTask(new Action(() =>
|
handler.InvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
||||||
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
||||||
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
||||||
}));
|
});
|
||||||
|
|
||||||
//Pre-1.14 Lighting data
|
//Pre-1.14 Lighting data
|
||||||
if (protocolversion < Protocol18Handler.MC114Version)
|
if (protocolversion < Protocol18Handler.MC114Version)
|
||||||
|
|
@ -192,10 +192,10 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
if (chunksContinuous && chunkMask == 0)
|
if (chunksContinuous && chunkMask == 0)
|
||||||
{
|
{
|
||||||
//Unload the entire chunk column
|
//Unload the entire chunk column
|
||||||
handler.ScheduleTask(new Action(() =>
|
handler.InvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
handler.GetWorld()[chunkX, chunkZ] = null;
|
handler.GetWorld()[chunkX, chunkZ] = null;
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -214,12 +214,12 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
chunk[blockX, blockY, blockZ] = new Block(queue.Dequeue());
|
chunk[blockX, blockY, blockZ] = new Block(queue.Dequeue());
|
||||||
|
|
||||||
//We have our chunk, save the chunk into the world
|
//We have our chunk, save the chunk into the world
|
||||||
handler.ScheduleTask(new Action(() =>
|
handler.InvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
||||||
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
||||||
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,10 +248,10 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
if (chunksContinuous && chunkMask == 0)
|
if (chunksContinuous && chunkMask == 0)
|
||||||
{
|
{
|
||||||
//Unload the entire chunk column
|
//Unload the entire chunk column
|
||||||
handler.ScheduleTask(new Action(() =>
|
handler.InvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
handler.GetWorld()[chunkX, chunkZ] = null;
|
handler.GetWorld()[chunkX, chunkZ] = null;
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -297,12 +297,12 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
|
for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
|
||||||
chunk[blockX, blockY, blockZ] = new Block(blockTypes.Dequeue(), blockMeta.Dequeue());
|
chunk[blockX, blockY, blockZ] = new Block(blockTypes.Dequeue(), blockMeta.Dequeue());
|
||||||
|
|
||||||
handler.ScheduleTask(new Action(() =>
|
handler.InvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
if (handler.GetWorld()[chunkX, chunkZ] == null)
|
||||||
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn();
|
||||||
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk;
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,24 @@ namespace MinecraftClient.Protocol
|
||||||
ILogger GetLogger();
|
ILogger GetLogger();
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="task">Task to run</param>
|
/// <param name="task">Task to run with any type or return value</param>
|
||||||
/// <returns>Any result returned from delegate</returns>
|
/// <returns>Any result returned from task, result type is inferred from the task</returns>
|
||||||
object ScheduleTask(Delegate task);
|
/// <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>
|
/// <summary>
|
||||||
/// Called when a network packet received or sent
|
/// Called when a network packet received or sent
|
||||||
|
|
|
||||||
|
|
@ -6,42 +6,121 @@ using System.Threading;
|
||||||
|
|
||||||
namespace MinecraftClient
|
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;
|
/// <summary>
|
||||||
|
/// Create a new asynchronous task with return value
|
||||||
public TaskWithResult(Delegate task)
|
/// </summary>
|
||||||
|
/// <param name="task">Delegate with return value</param>
|
||||||
|
public TaskWithResult(Func<T> task)
|
||||||
{
|
{
|
||||||
Task = task;
|
this.task = task;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Execute the delegate and set the <see cref="Result"/> property to the returned value
|
/// Check whether the task has finished running
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Value returned from delegate</returns>
|
public bool HasRun
|
||||||
public object Execute()
|
|
||||||
{
|
{
|
||||||
Result = Task.DynamicInvoke();
|
get
|
||||||
return Result;
|
{
|
||||||
|
return taskRun;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Block the program execution
|
/// Get the task result (return value of the inner delegate)
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Resume the program execution
|
/// Get the exception thrown by the inner delegate, if any
|
||||||
/// </summary>
|
/// </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