Awaiting on a task from a different class - c#

I have a singleton class and a property that gets set from another class (class b), no problem. I want a different class (class a) to wait indefinitely until the property in the singleton class transitions true. I want the most efficient way possible of doing this, so I felt tasks were ideal, but I can't effectively put all of the pieces together. I don't want to continue to poll and sleep thread.sleep.
public class A
{
public static void Main(string[] args)
{
if(!Monitor.Instance.HasChanged)
{
//await until the task in the Monitor class is complete
}
}
}
public class Monitor
{
private static Monitor instance;
private bool _hasChanged;
private Monitor() { }
public static Monitor Instance
{
get
{
if (instance == null)
{
instance = new Monitor();
}
return instance;
}
}
public bool HasChanged
{
get
{
return _hasChanged;
}
set
{
_hasChanged = value;
if (_hasChanged)
{
//kick off a task
}
}
}
}
public class B
{
private static readonly Monitor _instance;
public void DoSomething()
{
Monitor.Instance.HasChanged = true;
}
}

I would use a TaskCompletionSource for this. You would do something like:
public class Monitor
{
private TaskCompletionSource<bool> _changedTaskSource = new TaskCompletionSource<bool>();
public Task HasChangedTask => _changedTaskSource.Task;
public bool HasChanged
...
set
{
...
_changedTaskSource.TrySetResult(true);
}
}
This sets up a task completion source and completes the task when the value changes. You would wait on it like so:
await Monitor.Instance.HasChangedTask;
One thing that is not clear from your question and you will need to address is resetting the task. To do so, just re-create the TaskCompletionSource.

Related

Prevent static property being accessed before async function initializes it

I have a function that asynchronously loads a xml file, parses it, and adds certain values to a list. I'm using async and await for this. The issue I've run into is that after calling await the program moves on to executing code that accesses that list before the async function has finished adding all items.
My static class with async function:
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Xml.Linq;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.AddressableAssets;
namespace Drok.Localization
{
public static class Localization
{
/// <summary>
/// The currently available languages.
/// </summary>
public static List<string> Available { get; private set; } = new List<string>();
/// <summary>
/// The currently selected language.
/// </summary>
public static string Current { get; private set; } = null;
public static async Task Initialize()
{
await LoadMetaData();
}
private static async Task LoadMetaData()
{
AsyncOperationHandle<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>("Localization/meta.xml");
TextAsset metaDataFile = await handle.Task;
XDocument metaXMLData = XDocument.Parse(metaDataFile.text);
IEnumerable<XElement> elements = metaXMLData.Element("LangMeta").Elements();
foreach (XElement e in elements)
{
string lang = e.Attribute("lang").Value;
int id = Int32.Parse(e.Attribute("id").Value);
Debug.LogFormat("Language {0} is availible with id {1}.", lang, id);
Available.Add(lang);
}
}
public static void LoadLanguage(string lang)
{
Current = lang;
throw new NotImplementedException();
}
public static string GetString(string key)
{
return key;
}
}
}
The class that initializes it and accesses the list:
using Drok.Localization;
using UnityEngine;
namespace Spellbound.Menu
{
public class LanguageMenu : MonoBehaviour
{
private async void Awake()
{
await Localization.Initialize();
}
private void Start()
{
Debug.Log(Localization.Available.Count);
}
private void Update()
{
}
}
}
I have no idea how to prevent access to that list until after all items have been added. The code I posted just collects info on what languages are available so that only the one language being used can be loaded later.
A Task<T> represents some value (of type T) that will be determined in the future. If you make your property this type, then it will force all callers to await for it to be loaded:
public static class Localization
{
public static Task<List<string>> Available { get; private set; }
static Localization() => Available = LoadMetaDataAsync();
private static async Task<List<string>> LoadMetaDataAsync()
{
var results = new List<string>();
...
results.Add(lang);
return results;
}
}
Usage:
private async Task StartAsync()
{
var languages = await Localization.Available;
Debug.Log(languages.Available.Count);
}
One possibility might be to add some logic to wait for the metadata to be loaded when returning the list from the get accessor.
One way to do this is to have a bool field that is set to true when the list is ready, and then we either return a private backing List<string> or null, depending on the value of our bool field:
public static class Localization
{
private static bool metadataLoaded = false;
private static List<string> available = new List<string>();
// The 'Available' property returns null until the private list is ready
public static List<string> Available => metadataLoaded ? available : null;
private static async Task LoadMetaData()
{
// Add items to private 'available' list here
// When the list is ready, set our field to 'true'
metadataLoaded = true;
}
}
The Awake method is async void, so there is no way for the caller to guarantee that it finishes before moving on to something else.
However, you could preserve the task and await it in the Start method to ensure that it is completed. Awaiting it twice does not harm anything.
public class LanguageMenu : MonoBehaviour
{
private Task _task;
private async void Awake()
{
_task = Localization.Initialize();
await _task;
}
private async void Start()
{
await _task;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
}
}
Expanding on Rufus' comment:
Declare a bool property that's initialized to false. And in your list's getter, return the list only if the said bool property is true, and return maybe null (this depends on your requirements) if false.
public static bool IsAvailable { get; set; } = false;
private static List<string> _available;
public static List<string> Available
{
get
{
if (IsAvailable)
return _available;
else
return null;
}
set { _available = value; }
}
Finally, in your async function, when the work is done set the above property to true.
Latest when there is an Update method involved that should also wait with its execution using async and await might not be enough anyway.
Usually there is always one big alternative to using async for the Unity messages: an event system like e.g.
public static class Localization
{
public static event Action OnLocalizationReady;
public static async void Initialize()
{
await LoadMetaData();
OnLocalizationReady?.Invoke();
}
...
}
And wait for that event in any class using it like e.g.
public class LanguageMenu : MonoBehaviour
{
private bool locaIsReady;
private void Awake()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
Localization.OnLocalizationReady += OnLocalizationReady;
Localization.Initialize();
}
private void OnDestroy ()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
}
// This now replaces whatever you wanted to do in Start originally
private void OnLocalizationReady ()
{
locaIsReady = true;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
// Block execution until locaIsReady
if(!locaIsReady) return;
...
}
}
Or for minimal better performance you could also set enabled = false in Awake and set it to true in OnLocalizationReady then you could get rid of the locaIsReady flag.
No async and await needed.
If you would move the Localization.Initialize(); instead to Start you would give other classes the chance to also add some callbacks before to Localization.OnLocalizationReady in Awake ;)
And you can extend this in multiple ways! You could e.g. together with firering the event directly also pass in the reference to Availables so listeners can directly use it like e.g.
public static class Localization
{
public static event Action<List<string>> OnLocalizationReady;
...
}
and then in LanguageMenu change the signiture of OnLocalizationReady to
public class LanguageMenu : MonoBehaviour
{
...
// This now replaces whatever you wanted to do in Start originally
private void OnLocalizationReady(List<string> available)
{
locaIsReady = true;
Debug.Log(available.Count);
}
}
If anyway LanguageMenu will be the only listener then you could even pass the callback directly as parameter to Initialize like
public static class Localization
{
public static async void Initialize(Action<List<string>> onSuccess)
{
await LoadMetaData();
onSuccess?.Invoke();
}
...
}
and then use it like
private void Awake()
{
Localization.Initialize(OnLocalizationReady);
}
private void OnLocalizationReady(List<string>> available)
{
locaIsReady = true;
Debug.Log(available.Count);
}
or as lambda expression
private void Awake()
{
Localization.Initialize(available =>
{
locaIsReady = true;
Debug.Log(available .Count);
}
}
Update
As to your question about later Initialization: Yes there is a simple fix as well
public static class Localization
{
public static event Action OnLocalizationReady;
public static bool isInitialized;
public static async void Initialize()
{
await LoadMetaData();
isInitialized = true;
OnLocalizationReady?.Invoke();
}
...
}
Then in other classes you can do it conditional either use callbacks or Initialize right away:
private void Awake()
{
if(Localization.isInitialized)
{
OnLocaInitialized();
}
else
{
Localization.OnInitialized -= OnLocaInitialized;
Localization.OnInitialized += OnLocaInitialized;
}
}
private void OnDestroy ()
{
Localization.OnInitialized -= OnLocaInitialized;
}
private void OnLocaInitialized()
{
var available = Localization.Available;
...
}
private void Update()
{
if(!Localization.isInitialized) return;
...
}

Implement AsyncManualResetEvent using Lazy<T> to determine if the task has been awaited

I'm implementing an AsyncManualResetEvent based on Stephen Toub's example. However, I would like to know if the event, or specifically, the underlying Task<T> has been waited on.
I've already investigated the Task class, and there doesn't seem to be a sensible way to determine if it has ever been 'awaited' or if a continuation has been added.
In this case however, I control access to the underlying task source, so I can listen for any calls to the WaitAsync method instead. In thinking about how to do this, I decided to use a Lazy<T> and just see if it has been created.
sealed class AsyncManualResetEvent {
public bool HasWaiters => tcs.IsValueCreated;
public AsyncManualResetEvent() {
Reset();
}
public Task WaitAsync() => tcs.Value.Task;
public void Set() {
if (tcs.IsValueCreated) {
tcs.Value.TrySetResult(result: true);
}
}
public void Reset() {
tcs = new Lazy<TaskCompletionSource<bool>>(LazyThreadSafetyMode.PublicationOnly);
}
Lazy<TaskCompletionSource<bool>> tcs;
}
My question then, is whether this is a safe approach, specifically will this guarantee that there are never any orphaned/lost continuations while the event is being reset?
If you truly wanted to know if anyone called await on your task (not just the fact that they called WaitAsync()) you could make a custom awaiter that acts as a wrapper for the TaskAwaiter that is used by m_tcs.Task.
public class AsyncManualResetEvent
{
private volatile Completion _completion = new Completion();
public bool HasWaiters => _completion.HasWaiters;
public Completion WaitAsync()
{
return _completion;
}
public void Set()
{
_completion.Set();
}
public void Reset()
{
while (true)
{
var completion = _completion;
if (!completion.IsCompleted ||
Interlocked.CompareExchange(ref _completion, new Completion(), completion) == completion)
return;
}
}
}
public class Completion
{
private readonly TaskCompletionSource<bool> _tcs;
private readonly CompletionAwaiter _awaiter;
public Completion()
{
_tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_awaiter = new CompletionAwaiter(_tcs.Task, this);
}
public CompletionAwaiter GetAwaiter() => _awaiter;
public bool IsCompleted => _tcs.Task.IsCompleted;
public bool HasWaiters { get; private set; }
public void Set() => _tcs.TrySetResult(true);
public struct CompletionAwaiter : ICriticalNotifyCompletion
{
private readonly TaskAwaiter _taskAwaiter;
private readonly Completion _parent;
internal CompletionAwaiter(Task task, Completion parent)
{
_parent = parent;
_taskAwaiter = task.GetAwaiter();
}
public bool IsCompleted => _taskAwaiter.IsCompleted;
public void GetResult() => _taskAwaiter.GetResult();
public void OnCompleted(Action continuation)
{
_parent.HasWaiters = true;
_taskAwaiter.OnCompleted(continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
_parent.HasWaiters = true;
_taskAwaiter.UnsafeOnCompleted(continuation);
}
}
}
Now if anyone registered a continuation with OnCompleted or UnsafeOnCompleted the bool HasWaiters will become true.
I also added TaskCreationOptions.RunContinuationsAsynchronously to fix the issue Stephen fixes with the Task.Factory.StartNew at the end of the article (It was introduced to .NET after the article was written).
If you just want to see if anyone called WaitAsync you can simplify it a lot, you just need a class to hold your flag and your completion source.
public class AsyncManualResetEvent
{
private volatile CompletionWrapper _completionWrapper = new CompletionWrapper();
public Task WaitAsync()
{
var wrapper = _completionWrapper;
wrapper.WaitAsyncCalled = true;
return wrapper.Tcs.Task;
}
public bool WaitAsyncCalled
{
get { return _completionWrapper.WaitAsyncCalled; }
}
public void Set() {
_completionWrapper.Tcs.TrySetResult(true); }
public void Reset()
{
while (true)
{
var wrapper = _completionWrapper;
if (!wrapper.Tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref _completionWrapper, new CompletionWrapper(), wrapper) == wrapper)
return;
}
}
private class CompletionWrapper
{
public TaskCompletionSource<bool> Tcs { get; } = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
public bool WaitAsyncCalled { get; set; }
}
}

Using locks to access a method in C#

I have a method A which call another method B. Upon clicking on a button, method A is called which in turn calls method B. However, when 2 users click on the button simultaneously, I want only one user to access method B while the other waits for method B to complete. I thought of doing it this way:
private static Object _Lock = new Object();
private void A(){
lock(_Lock){
B();
}
}
The users are on different machines. The project is a web site.
But I think this is not correct. How can I improve the above code so that it is the proper way to work?
I agree with #Torestergaard, you should keep the lock as slim as possible. Therefor if taking the code sample provided above by #Rebornx and modifying it a bit you can use something like below example:
public class Program
{
public static void Main()
{
LockSample lockSampleInstance = LockSample.GetInstance();
lockSampleInstance.MethodA();
}
}
public class LockSample
{
private static readonly LockSample INSTANCE = new LockSample();
private static Object lockObject = new Object();
public static LockSample GetInstance()
{
return INSTANCE;
}
public void MethodA()
{
Console.WriteLine("MethodA Called");
MethodB();
}
private void MethodB()
{
lock(lockObject)
{
Console.WriteLine("MethodB Called");
}
}
}
Hope it will help,
Liron
Here is a simple program, I used single ton pattern. You can achieve the locking by using "Monitor" also.
public class Program
{
public static void Main()
{
LockSample lockObject = LockSample.GetInstance();
lock(lockObject)
{
lockObject.MethodA();
}
}
}
public class LockSample
{
private static LockSample _Lock;
public static LockSample GetInstance()
{
if(_Lock == null)
{
_Lock = new LockSample();
}
return _Lock;
}
public void MethodA()
{
Console.WriteLine("MethodA Called");
MethodB();
}
private void MethodB()
{
Console.WriteLine("MethodB Called");
}
}
Generally you should keep you lock as slim as possible, so dependent on what you do then it might make sense to move the lock statement into method B only guarding the resource that doesn't support multiple parallel users.
But generally there is nothing wrong with your example.
You can declare the method B with this attribute:
[MethodImpl(MethodImplOptions.Synchronized)]
public void B() {
...
}

How to make a slightly modified AutoResetEvent class?

I need a synchronizing class that behaves exactly like the AutoResetEvent class, but with one minor exception:
A call to the Set() method must release all waiting threads, and not just one.
How can I construct such a class? I am simply out of ideas?
Martin.
So you have multiple threads doing a .WaitOne() and you want to release them?
Use the ManualResetEvent class and all the waiting threads should release...
Thank you very much for all your thougts and inputs which I have read with great interest. I did some more searching here on Stackoverflow, and suddenly I found this, whcih turned out to be just what I was looking for. By cutting it down to just the two methods I need, I ended up with this small class:
public sealed class Signaller
{
public void PulseAll()
{
lock (_lock)
{
Monitor.PulseAll(_lock);
}
}
public bool Wait(TimeSpan maxWaitTime)
{
lock (_lock)
{
return Monitor.Wait(_lock, maxWaitTime);
}
}
private readonly object _lock = new object();
}
and it does excactly what it should! I'm amazed that a solution could be that simple, and I love such simplicity. I'ts beautiful. Thank you, Matthew Watson!
Martin.
Two things you might try.
Using a Barrier object add conditionally adding threads too it and signaling them.
The other might be to use a publisher subscriber setup like in RX. Each thread waits on an object that it passes to a collection. When you want to call 'set' loop over a snapshot of it calling set on each member.
Or you could try bears.
If the event is being referenced by all threads in a common field or property, you could replace the common field or property with a new non-signaled event and then signal the old one. It has some cost to it since you'll be regularly creating new synchronization objects, but it would work. Here's an example of how I would do that:
public static class Example
{
private static volatile bool stopRunning;
private static ReleasingAutoResetEvent myEvent;
public static void RunExample()
{
using (Example.myEvent = new ReleasingAutoResetEvent())
{
WaitCallback work = new WaitCallback(WaitThread);
for (int i = 0; i < 5; ++i)
{
ThreadPool.QueueUserWorkItem(work, i.ToString());
}
Thread.Sleep(500);
for (int i = 0; i < 3; ++i)
{
Example.myEvent.Set();
Thread.Sleep(5000);
}
Example.stopRunning = true;
Example.myEvent.Set();
}
}
private static void WaitThread(object state)
{
while (!Example.stopRunning)
{
Example.myEvent.WaitOne();
Console.WriteLine("Thread {0} is released!", state);
}
}
}
public sealed class ReleasingAutoResetEvent : IDisposable
{
private volatile ManualResetEvent manualResetEvent = new ManualResetEvent(false);
public void Set()
{
ManualResetEvent eventToSet = this.manualResetEvent;
this.manualResetEvent = new ManualResetEvent(false);
eventToSet.Set();
eventToSet.Dispose();
}
public bool WaitOne()
{
return this.manualResetEvent.WaitOne();
}
public bool WaitOne(int millisecondsTimeout)
{
return this.manualResetEvent.WaitOne(millisecondsTimeout);
}
public bool WaitOne(TimeSpan timeout)
{
return this.manualResetEvent.WaitOne(timeout);
}
public void Dispose()
{
this.manualResetEvent.Dispose();
}
}
Another more lightweight solution you could try that uses the Monitor class to lock and unlock objects is below. However, I'm not as happy with the cleanup story for this version of ReleasingAutoResetEvent since Monitor may hold a reference to it and keep it alive indefinitely if it is not properly disposed.
There are a few limitations/gotchas with this implementation. First, the thread that creates this object will be the only one that will be able to signal it with a call to Set; other threads that attempt to do the same thing will receive a SynchronizationLockException. Second, the thread that created it will never be able to wait on it successfully since it already owns the lock. This will only be an effective solution if you have exactly one controlling thread and several other waiting threads.
public static class Example
{
private static volatile bool stopRunning;
private static ReleasingAutoResetEvent myEvent;
public static void RunExample()
{
using (Example.myEvent = new ReleasingAutoResetEvent())
{
WaitCallback work = new WaitCallback(WaitThread);
for (int i = 0; i < 5; ++i)
{
ThreadPool.QueueUserWorkItem(work, i.ToString());
}
Thread.Sleep(500);
for (int i = 0; i < 3; ++i)
{
Example.myEvent.Set();
Thread.Sleep(5000);
}
Example.stopRunning = true;
Example.myEvent.Set();
}
}
private static void WaitThread(object state)
{
while (!Example.stopRunning)
{
Example.myEvent.WaitOne();
Console.WriteLine("Thread {0} is released!", state);
}
}
}
public sealed class ReleasingAutoResetEvent : IDisposable
{
private volatile object lockObject = new object();
public ReleasingAutoResetEvent()
{
Monitor.Enter(this.lockObject);
}
public void Set()
{
object objectToSignal = this.lockObject;
object objectToLock = new object();
Monitor.Enter(objectToLock);
this.lockObject = objectToLock;
Monitor.Exit(objectToSignal);
}
public void WaitOne()
{
object objectToMonitor = this.lockObject;
Monitor.Enter(objectToMonitor);
Monitor.Exit(objectToMonitor);
}
public bool WaitOne(int millisecondsTimeout)
{
object objectToMonitor = this.lockObject;
bool succeeded = Monitor.TryEnter(objectToMonitor, millisecondsTimeout);
if (succeeded)
{
Monitor.Exit(objectToMonitor);
}
return succeeded;
}
public bool WaitOne(TimeSpan timeout)
{
object objectToMonitor = this.lockObject;
bool succeeded = Monitor.TryEnter(objectToMonitor, timeout);
if (succeeded)
{
Monitor.Exit(objectToMonitor);
}
return succeeded;
}
public void Dispose()
{
Monitor.Exit(this.lockObject);
}
}

C# Generics or Reflection - calling a function of unknown class

I'm making a game where workers perform actions based on a current Task. Each worker will be assigned a list of tasks, in a preferred order (which is influenced by the player's decisions).
When a task is completed (e.g. take item from X to Y), the worker needs to start a new task by checking through their list of possible tasks, see if each can be performed, and if so, set their current task to that task and start it (the last task - "Wander Around" is always going to be available).
I currently have this working using a big switch statement and Enums, but now want to generalise this code to create a Task class, and give the workers a list of preferred Tasks, a GetNextTask() function, and in the worker's Update() method, call currentTask.update() (this will get the worker to do whatever he's required to do under the current task, and which will call worker.GetNextTask() when the task is complete).
What I'm unclear on is the best way to store Tasks in the worker.
Should I use:
1) Reflection. Store the possible Tasks as a list of types, then use reflection to a) call a static method public static virtual bool CanPerformThisTask() which is overridden in each subclass, and b) Create an instance of that task for the worker?
(example attempt at code for this below - but unable to test yet)
2) Instantiate all the Tasks whenever a worker needs to get a new task (probably using Activator), and check (Task)task.CanPerformThisTask() for each one - if true, do that task. Instantiating them all seems inefficient though?
3) Generics. Can this be done using generics? If so, how?
Here is a snippet of my classes to give the idea of what I'm trying to do:
Worker Class:
protected List<Point> waypoints = new List<Point>();
public bool reachedDestination { get { return waypoints.Count == 0; } }
protected Task task;
public List<Type> possibleTasks;
public Worker(Task initialTask, List<Type> initialPossibleTasks ...)
: base(...)
{
task = initialTask;
possibleTasks = initialPossibleTasks;
}
public override void Update()
{
base.Update();
if (!reachedDestination) Move();
task.Update();
}
public void GetNextTask()
{
foreach (Type t in possibleTasks)
{
//reflection code here - will this work and can we do this with generics instead?
Bool canDoT = (bool)t.GetMethod("CanPerformThisTask", BindingFlags.Static | BindingFlags.Public).Invoke(null, null);
if (canDoT)
{
task = Activator.CreateInstance(t);
return;
}
}
}
Here is some incomplete code for my base Task class (which shouldn't be instantiated):
public class Task
{
public Worker worker;
public virtual static bool CanPerformThisTask()
{
//never call this from here - always from subclasses
return false;
}
public Task()
{
//set up code here
}
public virtual void Update()
{
//make worker do relevant activities here
//call finish task when done
}
public void FinishTask()
{
worker.GetNextTask();
}
}
and here is an example of a Task the worker will have in its list of possible tasks:
public class T_WorkerWander : Task
{
public static override bool CanPerformThisTask()
{
//can always wander (other Tasks will have conditions here)
return true;
}
public T_WorkerWander()
: base()
{
}
override public void Update()
{
//make the worker wander here
if (worker.reachedDestination) FinishTask();
}
}
Update: here is the code I've now got working
Task Class:
public abstract class Task
{
//the entity holding this task
public TaskableEntity taskEntity;
public List<TaskStage> taskStages;
public TaskStage currentTaskStage { get { return taskStages[0]; } }
public Task(TaskableEntity t) { taskEntity = t; }
/// <summary>
/// the conditions for the Task to be started
/// </summary>
public virtual bool CanStart()
{
return true;
}
public void Start()
{
taskStages = new List<TaskStage>();
InitialiseTaskStages();
taskStages[0].Start();
}
public abstract void InitialiseTaskStages();
public void Update()
{
currentTaskStage.Update();
if (currentTaskStage.IsComplete()) TaskStageComplete();
}
public void TaskStageComplete()
{
taskStages.RemoveAt(0);
if (taskStages.Count == 0) taskEntity.TaskComplete();
else currentTaskStage.Start();
}
public void SetTaskStages(params TaskStage[] t)
{
taskStages = t.ToList();
}
public void Interrupt()
{
currentTaskStage.Interrupt();
}
}
TaskStage class:
public sealed class TaskStage
{
private Task task;
private List<Point> pointsToMoveTo;
public void SetPointsToMoveTo(Point p) { pointsToMoveTo = new List<Point>() { p }; }
public void SetPointsToMoveTo(params Point[] p) { pointsToMoveTo = p.ToList(); }
public void SetPointsToMoveTo(List<Point> p) { pointsToMoveTo = p; }
public Action actionToApply;
private float timeToWait;
public void SetWait(float wait) { timeToWait = wait; }
private IReservable[] itemsToReserve;
public void SetItemsToReserve(params IReservable[] items) { itemsToReserve = items; }
private IReservable[] itemsToUnreserve;
public void SetItemsToUnreserve(params IReservable[] items) { itemsToUnreserve = items; }
private Emotion emotionToSet;
public void SetEmotionToSet(Emotion e) { emotionToSet = e; }
private TaskStage _interrupt;
public void SetInterruptAction(TaskStage t) { _interrupt = t; }
public void Interrupt() { _interrupt.Start(); }
public TaskStage(Task t)
{
task = t;
}
public void Start()
{
if (actionToApply != null) actionToApply();
if (itemsToUnreserve != null) UnreserveItems();
if (itemsToReserve != null) ReserveItems();
if (pointsToMoveTo != null)
{
//this will need changing after pathfinding sorted out...
if (pointsToMoveTo.Count == 1) task.taskEntity.SetWaypoints(pointsToMoveTo[0]);
else task.taskEntity.waypoints = pointsToMoveTo;
}
if (emotionToSet != null) emotionToSet.StartEmotion();
}
public void Update()
{
if (timeToWait > 0) timeToWait -= GV.elapsedTime;
}
public bool IsComplete()
{
if (pointsToMoveTo != null && !task.taskEntity.reachedDestination) return false;
if (timeToWait > 0) return false;
return true;
}
public void ReserveItems()
{
foreach (IReservable i in itemsToReserve)
{
i.reserved = true;
}
}
public void UnreserveItems()
{
foreach (IReservable i in itemsToUnreserve)
{
i.reserved = false;
}
}
}
Example Task:
public class T_WorkerGoToBed : Task
{
public FactoryWorker worker { get { return taskEntity as FactoryWorker; } }
public T_WorkerGoToBed(TaskableEntity t)
: base(t) { }
public override bool CanStart()
{
return Room.Available<Bed>(GV.Bedrooms);
}
public override void InitialiseTaskStages()
{
Bed bedToSleepIn = Room.NearestAvailableFurniture<Bed>(GV.Bedrooms, taskEntity.X, taskEntity.Y);
//stage 1 - reserve bed and move there
TaskStage ts1 = new TaskStage(this);
ts1.SetItemsToReserve(bedToSleepIn);
ts1.SetPointsToMoveTo(bedToSleepIn.XY);
//stage 2 - sleep in bed
TaskStage ts2 = new TaskStage(this);
ts2.SetWait((worker.maxEnergy - worker.energy) / worker.energyRegeneratedPerSecondWhenSleeping);
ts2.SetEmotionToSet(new E_Sleeping(worker, false));
//stage 3 - unreserve bed
TaskStage ts3 = new TaskStage(this);
ts3.SetItemsToUnreserve(bedToSleepIn);
ts3.SetEmotionToSet(new E_Happy(worker, false));
SetTaskStages(ts1, ts2, ts3);
}
}
It sounds like you need to reverse responsibility between task and worker. Instead of asking whether the task can be performed, ask the worker if he can perform a given task:
class Worker
{
bool CanPerformTask<T>() where T : Task
{
var type = typeof(T);
// code to determine whether worker can perform the task T
}
// alternative with instance parameter
bool CanPerformTask<T>( T task ) where T : Task
{
// code to determine whether worker can perform the task passed in
}
}
This solution avoids the "instantiate all tasks or call static method" problem.
Also, consider using the built-in collection classes. Things such as queues and stacks can greatly simplify the code needed to schedule execution of things.
I think you are abusing the point of static classes. The "Task" class should be standard (not static). Your "Worker" class is not static therefore implying that there is more than one "Worker" instance. Given this paradigm, these workers can probably have the same task assigned to them.
Your "Worker" class needs to have this property modified from:
public List possibleTasks;
to
public List _possibleTasks;
You probably should not have public access to this property either. You can modify "CanPerformThisTask" as necessary.

Categories