I'm writing a game in Unity where one has different game modes but eventually uses the same level, it is only that things behave differently.
The difference is only in how the player has to aim for example, so s/he has to quickly find the same places for the targets (the targets appear by the same order) or they appear randomly.
It is the same stage but with different rules so I thought an interface would be beneficial - the GameManager class implements it based on the game mode. Of course I could write different Game Managers or use a switch in one but I wish to keep things organized for a change.
Question is, how to do it?
This is how you setup a simple state machine:
//state
public enum GameMode { Normal, Endless, Campaign, Idle, Count }
private GameMode gameMode;
//state machine array
private delegate void UpdateDelegate();
private UpdateDelegate[] UpdateDelegates;
void Awake()
{
//setup all UpdateDelegates here to avoid runtime memory allocation
UpdateDelegates = new UpdateDelegate[(int)GameMode.Count];
//and then each UpdateDelegate
UpdateDelegates[(int)GameMode.Normal] = UpdateNormalState;
UpdateDelegates[(int)GameMode.Endless] = UpdateEndlessState;
UpdateDelegates[(int)GameMode.Campaign] = UpdateCampaignState;
UpdateDelegates[(int)GameMode.Idle] = UpdateIdleState
gameMode = GameMode.Idle;
}
void Update()
{
//call the update method of current state
if(UpdateDelegates[(int)gameMode]!=null)
UpdateDelegates[(int)gameMode]();
}
Now you can separate the logic of each state:
void UpdateNormalState() {
//...
//write logic for normal state
}
//...
//same for other states
This way when you change gameMode, the update method of new state will be iteratively called after end of current frame.
for more info you can watch this video
The good thing about state machines is that they are easy to handle (compared with switch-case or many ifs). you have an array of methods and can do anything you want with them and still be sure that only one of them can run at a time. the maximum delay for changing states is always as short as Time.deltaTime (if Update method is used to call state machine methods)
You can even make the state machine 2D. but make sure you assign all of UpdateDelegates
public enum GameMode { Normal, Endless, Campaign, Idle, Count }
public enum GameState { Playing, Paused, GameOver, Idle, Count }
private UpdateDelegate[,] UpdateDelegates;
UpdateDelegates = new UpdateDelegate[(int)GameMode.Count, (int)GameState.Count];
If it's not enough for your game you can use an advanced state machine. here's a sample code I copied from somewhere and haven't tested yet:
This approach uses transitions between states. e.g. call MoveNext with a given Command and it changes the state to next ProcessState considering the current ProcessState of state machine and the given command.
using System;
using System.Collections.Generic;
namespace Juliet
{
public enum ProcessState
{
Inactive,
Active,
Paused,
Terminated
}
public enum Command
{
Begin,
End,
Pause,
Resume,
Exit
}
public class Process
{
class StateTransition
{
readonly ProcessState CurrentState;
readonly Command Command;
public StateTransition(ProcessState currentState, Command command)
{
CurrentState = currentState;
Command = command;
}
public override int GetHashCode()
{
return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
}
public override bool Equals(object obj)
{
StateTransition other = obj as StateTransition;
return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
}
}
Dictionary<StateTransition, ProcessState> transitions;
public ProcessState CurrentState { get; private set; }
public Process()
{
CurrentState = ProcessState.Inactive;
transitions = new Dictionary<StateTransition, ProcessState>
{
{ new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
{ new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
{ new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
{ new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
{ new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
{ new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
};
}
public ProcessState GetNext(Command command)
{
StateTransition transition = new StateTransition(CurrentState, command);
ProcessState nextState;
if (!transitions.TryGetValue(transition, out nextState))
throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
return nextState;
}
public ProcessState MoveNext(Command command)
{
CurrentState = GetNext(command);
return CurrentState;
}
}
public class Program
{
static void Main(string[] args)
{
Process p = new Process();
Console.WriteLine("Current State = " + p.CurrentState);
Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
Console.ReadLine();
}
}
}
Related
I am currently developing a multiplayer web game with timer using ASP.NET Core. For real time communication I am using SignalR. Everythning up to the moment had been working just fine.
The way I implemented the timer functionality was the following: I created a static dictionary where the key is the Id of the game and the value is the timer corresponding to the given game. Then I exposed public methods for managing the state of the dictionary:
public class TimerManager
{
private static readonly Dictionary<string, GameTimer> gameTimersByGameIds = new();
public void AttachTimerToGameState(GameTimer timer, string gameId)
{
if (!gameTimersByGameIds.ContainsKey(gameId))
{
gameTimersByGameIds.Add(gameId, timer);
return;
}
gameTimersByGameIds[gameId] = timer;
}
public GameTimer? GetTimer(string gameId)
{
if (!gameTimersByGameIds.ContainsKey(gameId))
{
return null;
}
return gameTimersByGameIds[gameId];
}
public GameTimer CreateTimer(GameState gameState)
{
if (gameState.RemainingSecondsByUserNames.Count == 0)
{
return ActivatorUtilities.CreateInstance<StandardTimer>(_serviceProvider, gameState);
}
else
{
return ActivatorUtilities.CreateInstance<ChessTimer>(_serviceProvider, gameState, _gamesService);
}
}
}
I created my own base GameTimer class which encapsulates a System.Timers.Timer inside of it:
public abstract class GameTimer
{
protected readonly System.Timers.Timer _timer = new();
public virtual void Reset()
{
_timer.AutoReset = true;
_timer.Interval = 1_000;
}
public virtual void Start()
{
_timer.Start();
}
public virtual void Dispose()
{
_timer.Dispose();
}
}
Then I inherited from the abstract GameTimer class to create different types of timers.
public class StandardTimer : GameTimer
public class ChessTimer : GameTimer
The problem is inside of the ChessTimer class:
public class ChessTimer : GameTimer
{
private readonly GameState _gameState;
private readonly IGameService _gameService;
private readonly IHubContext<GameHub, IGameClient> _hubContext;
private readonly IMatchmakingService _matchmakingService;
private readonly IGamesService _gamesService;
public ChessTimer(
GameState gameState,
IGamesService gamesService,
IGameService gameService,
IHubContext<GameHub, IGameClient> hubContext,
IMatchmakingService matchmakingService)
{
_gameState = gameState;
_gameService = gameService;
_hubContext = hubContext;
_matchmakingService = matchmakingService;
_gamesService = gamesService;
Reset();
_timer.Elapsed += async (sender, args) => await OnTimedEvent(sender, args);
}
public int SecondsRemaining { get; private set; }
public override void Reset()
{
string currentPlayerName = _gameState.CurrentTeam.CurrentPlayer.UserName;
SecondsRemaining = _gameState.RemainingSecondsByUserNames[currentPlayerName];
base.Reset();
}
private async Task OnTimedEvent(object? sender, ElapsedEventArgs args)
{
if (SecondsRemaining >= 0)
{
string currentPlayerUserName = _gameState.CurrentTeam.CurrentPlayer.UserName;
_gameState.RemainingSecondsByUserNames[currentPlayerUserName] = SecondsRemaining;
int minutes = SecondsRemaining / 60;
int seconds = SecondsRemaining % 60;
var viewModel = new UpdateGameTimerViewModel
{
Minutes = minutes,
Seconds = seconds,
};
foreach (Player player in _gameState.Players)
{
if (player.ConnectionId == null)
{
continue;
}
await _hubContext.Clients
.Client(player.ConnectionId)
.UpdateGameTimer(viewModel);
}
SecondsRemaining--;
return;
}
if (_gameState.RemainingSecondsByUserNames.All(x => x.Value <= 0))
{
_gameState.EndGame();
}
else
{
_gameState.NextTeam();
}
foreach (Player player in _gameState.Players)
{
_gameService.FillPlayerTiles(_gameState, player);
}
foreach (Player player in _gameState.Players)
{
if (player.ConnectionId == null)
{
continue;
}
var viewModel = _gameService.MapFromGameState(_gameState, player.UserName);
await _hubContext.Clients
.Client(player.ConnectionId)
.UpdateGameState(viewModel);
if (_gameState.IsGameOver)
{
_matchmakingService.RemoveUserFromGame(player.UserName);
await _hubContext.Groups
.RemoveFromGroupAsync(
player.ConnectionId, _gameState.GameId);
}
}
if (_gameState.IsGameOver)
{
_matchmakingService.RemoveGameState(_gameState.GameId);
await _gamesService!.SaveGameAsync(new SaveGameInputModel
{
GameId = _gameState.GameId,
Players = _gameState.Players
});
Dispose();
}
Reset();
}
}
Basically the way my OnTimedEvent method works is that it executes every second. Then checks whether the time of all players has run out. If it hasn't it updates the state of the game, otherwise is ends the game. After everything else is done, I try to save the game inside my database in order to enable the users to see summary about their played games. I am using a class called GamesService for this task. It uses UserManager internally to perform some work with the Users in the database.
if (_gameState.IsGameOver)
{
_matchmakingService.RemoveGameState(_gameState.GameId);
await _gamesService!.SaveGameAsync(new SaveGameInputModel
{
GameId = _gameState.GameId,
Players = _gameState.Players
});
Dispose();
}
Reset();
The problem is that when the game ends and the above code is executed it produces the following exception:
Unhandled exception. Unhandled exception. System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'UserManager`1'.
at Microsoft.AspNetCore.Identity.UserManager`1.ThrowIfDisposed()
at Microsoft.AspNetCore.Identity.UserManager`1.FindByNameAsync(String userName)
at SuperScrabble.Services.Data.Users.UsersService.GetByUserNameAsync(String userName) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\Services\SuperScrabble.Services.Data\Users\UsersService.cs:line 64
at SuperScrabble.Services.Data.Games.GamesService.SaveGameAsync(SaveGameInputModel input) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\Services\SuperScrabble.Services.Data\Games\GamesService.cs:line 52
at SuperScrabble.WebApi.Timers.ChessTimer.OnTimedEvent(Object sender, ElapsedEventArgs args) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\WebApi\SuperScrabble.WebApi\Timers\ChessTimer.cs:line 120
at SuperScrabble.WebApi.Timers.ChessTimer.<.ctor>b__5_0(Object sender, ElapsedEventArgs args) in C:\Users\georg\Source\Github\SuperScrabble\src\Server\WebApi\SuperScrabble.WebApi\Timers\ChessTimer.cs:line 35
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi)
at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext, Action`1 callback, TState& state)
at System.Threading.QueueUserWorkItemCallback.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
at System.Threading.Thread.StartCallback()
It seems to me that the _gamesService object which I pass to the CreateInstance() method inside the TimerManager.CreateTimer() method is already disposed or that some of the services it uses internally are.
public GameTimer CreateTimer(GameState gameState)
{
if (gameState.RemainingSecondsByUserNames.Count == 0)
{
return ActivatorUtilities.CreateInstance<StandardTimer>(_serviceProvider, gameState);
}
else
{
return ActivatorUtilities.CreateInstance<ChessTimer>(_serviceProvider, gameState, _gamesService);
}
}
I am not sure if what I am trying to do is correct. I need to use service classes inside my timer classes to perform operations every time the timer ticks. However, I cannot use dependency injection and that's the reason why I use the ActivatorUtilities class to instantiate the object.
I create all timers from a method inside my SignalR hub class:
private async Task StartGameAsync()
{
var gameState = _matchmakingService.GetGameState(UserName);
string gameId = gameState.GameId;
foreach (Player player in gameState.Players)
{
await Groups.AddToGroupAsync(player.ConnectionId!, gameId);
}
var timer = _timerManager.CreateTimer(gameState);
Console.WriteLine(timer.GetType().Name);
_timerManager.AttachTimerToGameState(timer, gameId);
await Clients.Group(gameId).StartGame(gameId);
await UpdateGameStateAsync(gameState);
timer.Start();
}
So my question would be: Is it possible to find a work around of the problem?
If you need any more clarification please feel free to ask me anything. Any help would be greatly appreciated! Thanks
Iv'e created a timer using dispatcher time :
time = new DispatcherTimer();
time.Interval = new TimeSpan(0, 0, 0, 0, 80);
and I use it for the speed of an object.Each tick the objects moves 10 pixels.
I would like to know how to increase the speed of the object without changing the pixels it moves each tick, meaning I want to make the timer itself faster during run time every 10 seconds or so.
Is there a way I can do it?Ive tried making a variable speed=0 and increasing it each time I count 10 and then
time.Interval = new TimeSpan(0, 0, 0, 0, 80-speed);
but The object stayed in the same speed.So do I have to make my own timer class instead of using the built in dispatcher time, if so how do I do that?or is there another solution for this?
I think that DispatcherTimer is not your best ally for this task. The class is by no means designed to execute actions at precise intervals.
I'll try to better explain myself: even if the DispatcherTimer, as its name suggests, dispatches actions timely and with a great precision, the dispatched actions will be queued and then executed when the underlying GUI thread decides to process them.
Normally, a GUI thread has a resolution of around 8ms (it's an approximation, but I don't think we need to measure this right now)... and you are using a starting Interval of 80ms that is going to decrease over time until it probably goes beyond that tolerance limit of 8ms or so. In the meanwhile, you are also repainting your interface (or part of it) over and over and this impacts the performance and the responsiveness of the GUI even more: if the GUI thread is busy repainting and that requires more than the Interval value to be accomplished, the next dispatched action will be processed only once the GUI thread completes the undergoing task.
If you need a more precise scheduling, avoiding hangings / losses of responsiveness / delayed actions, you need to use a timer class that runs in background like System.Threading.Timer (google for SyncronizationContext, that would be helpful) or System.Timers.Timer.
On the top of that, never play with intervals when showing a change in speed. Work with a fixed interval and increase/decrease the movement "size" in pixels. You should be able to calculate the delta without problems. Just to make things clearer: if I want to slow that the speed of an object doubled, I don't half the timer interval that draws the object, but I double the amount of pixels my object traverses at each step.
using System;
using System.Collections.Generic;
using System.Linq;
namespace CQRS_and_EventSourcing
{
internal class Program
{
//CQRS = command query responsibility segregation
//CQS= command query separation
//COMMAND
public class PersonStroge
{
Dictionary<int, Person> people;
}
public class Person
{
public int UniqueId;
public int age;
EventBroker broker;
public Person(EventBroker broker)
{
this.broker = broker;
broker.Commands += BrokerOnCommands;
broker.Queries += BrokeronQueries;
}
private void BrokeronQueries(object sender, Query query)
{
var ac = query as AgeQuery;
if (ac != null && ac.Target == this)
{
ac.Result = age;
}
}
private void BrokerOnCommands(object sender, Command command)
{
var cac = command as ChangeAgeCommand;
if (cac != null && cac.Target == this)
{
if (cac.Register)
broker.AllEvents.Add(new AgeChangedEvent(this, age, cac.Age));
age = cac.Age;
}
}
public bool CanVote => age >= 16;
}
public class EventBroker
{
//1. All events that happened.
public IList<Event> AllEvents = new List<Event>();
//2. Commands
public event EventHandler<Command> Commands;
//3. Query
public event EventHandler<Query> Queries;
public void Command(Command c)
{
Commands?.Invoke(this, c);
}
public T Query<T>(Query q)
{
Queries?.Invoke(this, q);
return (T)q.Result;
}
public void UndoLast()
{
var e = AllEvents.LastOrDefault();
var ac = e as AgeChangedEvent;
if (ac != null)
{
Command(new ChangeAgeCommand(ac.Target, ac.OldValue) { Register = false });
AllEvents.Remove(e);
}
}
}
public class Query
{
public object Result;
}
public class AgeQuery : Query
{
public Person Target;
}
public class Command : EventArgs
{
public bool Register = true;
}
public class ChangeAgeCommand : Command
{
public Person Target;
//public int TargetId;
public int Age;
public ChangeAgeCommand(Person target, int age)
{
Target = target;
Age = age;
}
}
public class Event
{
//backtrack
}
public class AgeChangedEvent : Event
{
public Person Target;
public int OldValue, NewValue;
public AgeChangedEvent(Person target, int oldValue, int newValue)
{
Target = target;
OldValue = oldValue;
NewValue = newValue;
}
public override string ToString()
{
return $"Age changed from {OldValue} to {NewValue}";
}
}
static void Main(string[] args)
{
var eb = new EventBroker();
var p = new Person(eb);
eb.Command(new ChangeAgeCommand(p, 123));
foreach (var e in eb.AllEvents)
{
Console.WriteLine(e);
}
//int age;
//age = eb.Query<int>(new AgeQuery { Target = p });
//Console.WriteLine(age);
//eb.UndoLast();
//foreach (var e in eb.AllEvents)
//{
// Console.WriteLine(e);
//}
//age = eb.Query<int>(new AgeQuery { Target = p });
//Console.WriteLine(age);
Console.ReadKey();
}
}
}
İf you couldnt make look at this repository;
[1]:https://github.com/kYasinAblay/DNesteruk.Additional.Lectures/blob/master/CQRS_and_EventSourcing/Program.cs
Among the functions provided by LeanTween,
Is there a function that functions like iTeeen's RotateBy?
(RotateBy(GameObject obj,Hashtable hash))
What I want to do is,
After completing the animation, the function is executed through the string.
For example, in a card-matching game,
If you click on the card, the following event will occur.
WordReviewManager.cs:
public void onTuchHandler(object obj, EventArgs e)
{
TouchEventTypes t_evt = e as TouchEventTypes;
Debug.Log("GameObject : " + t_evt.go + " / " + "Card : " + t_evt.card);
Debug.Log("Card Index : " + t_evt.card_idx + " / " + "Card UniqueIndex : " + t_evt.card_snum);
Debug.Log("================================================================================");
WordReviewUtil.testAni(this.gameObject, t_evt.go, t_evt.card, t_evt.delay, t_evt.complete);
}
The function that receives the event is shown below.
WordReviewUtil.cs:
public static Hashtable testAni(GameObject listener, GameObject go, Card card, float delay = 0f, string complete = "testGood")
{
Debug.Log("hello TEST ANIMATION ~~ ");
Hashtable hash = new Hashtable();
GameObject goz = go as GameObject;
Debug.Log("goz >>>> " + goz);
hash.Add("gameObj", goz);
hash.Add("onComplete", complete);
return hash;
}
Here is what I expect to see afterwards.
WordReviewManager.cs:
public void testGood(Hashtable table)
{
Debug.Log("hello Moto ~?!");
}
That is, the testGood function is executed.
iTween has a function that gives me the functionality I want.
Immediately, RotateBy function.
But I am currently using the LeanTween library, not iTween.
Also, the LeanTween library does not provide the functionality I want.
I just need to call the function. Without any parameters.
How do I implement what I want to implement?
The main design change between iTween and LeanTween for the complete callback is that iTween takes the method name as string and internally calls the evil SendMessage method.
LeanTween uses the Action delegate, which can be considered as pointer to a method. You can see in WordReviewManager.Start() how you can just assign the testGood method to the field Action onCompleteCallback of TouchEventTypes and feed that into your system.
public class Card : MonoBehaviour { }
public class TouchEventTypes : EventArgs
{
public Card card;
public float delay;
public Action onCompleteCallback;
}
public class WordReviewManager : MonoBehaviour
{
void Start()
{
TouchEventTypes e = new TouchEventTypes();
e.onCompleteCallback = testGood;
//TODO: feed this into your touch event system.
}
public void testGood()
{
Debug.Log("hello Moto ~?!");
}
public void onTouchHandler(object obj, EventArgs e)
{
TouchEventTypes eventArgs = e as TouchEventTypes;
WordReviewUtil.RotateCard(
card: eventArgs.card,
delay: eventArgs.delay,
callback: eventArgs.onCompleteCallback
);
}
}
public static class WordReviewUtil
{
public static void RotateCard(Card card, float delay, Action callback)
{
LTDescr tween = LeanTween.rotateAround(card.gameObject,
axis: new Vector3(0,0,1),
add: 360f,
time: 2.0f
).setDelay(delay);
tween.setOnComplete(callback);
}
}
This is my struct that I have created.
public struct Bar
{
private static float deltaTime = 1.0f;
private static bool AutoRun = false;
private static bool AutoRunBought = false;
private static bool Start = false;
// DELTA TIME
public float GetDeltaTime()
{
return deltaTime;
}
public void SetDeltaTime(float _dt)
{
deltaTime = _dt;
}
public void IncrementDeltaTime(float _deltaIn)
{
deltaTime += _deltaIn;
}
public void DecrementDeltaTime(float _deltaIn)
{
deltaTime -= _deltaIn;
}
// AUTO RUN
public bool GetAutoRun()
{
return AutoRun;
}
public void SetAutoRun(bool _autoBought)
{
AutoRunBought = _autoBought;
}
public bool GetAutoRunBought()
{
return AutoRun;
}
public void SetAutoRunBought(bool _autoBought)
{
AutoRunBought = _autoBought;
}
// START
public bool GetStart()
{
return Start;
}
public void SetStart(bool _start)
{
Start = _start;
}
}
In my other class I create an instance of that by calling
scr_Globals.Bar[] myBars = new scr_Globals.Bar[2];
in my Update I am doing
if (myBars[0].GetAutoRun() == true)
{
myBars[0].IncrementDeltaTime (incrementBar1);
if (myBars[0].GetDeltaTime () > 40.0f) {
myBars[0].SetDeltaTime (1.0f);
globals.IncrementTotalMoney(1.0f);
}
}
else
{
if (myBars[0].GetStart() == true)
{
myBars[0].IncrementDeltaTime (incrementBar1);
if (myBars[0].GetDeltaTime () > 40.0f) {
myBars[0].SetDeltaTime (1.0f);
globals.IncrementTotalMoney(1.0f);
myBars[0].SetStart(false);
}
}
}
This above code is done for both of the buttons, so I have the same code but for position 1 of the array.
and I have a button that is created from Unity's UI and when it is clicked it activated a function I have created that sets one of the bools on. That code looks like this
public void OnButton1Click()
{
myBars[0].SetStart (true);
}
Whenever the button is clicked and calls that function it sets both myBars[0] and myBars[1] SetStart to true. Any assistance is appreciated, thanks a lot.
Your fields are all static:
private static float deltaTime = 1.0f;
private static bool AutoRun = false;
private static bool AutoRunBought = false;
private static bool Start = false;
So if you write:
Bar x = new Bar();
Bar y = new Bar();
x.SetStart(true);
bool b = y.GetStart();
... then b will be true. The value returned by GetStart doesn't depend on the value you call it on at all...
You don't want those fields to be static - they're meant to represent part of the state of each value, right?
I'd actually advise against mutable structs too, but that's a different matter. I'd also advise against all those GetXyz/SetXyz methods - learn about C# properties instead.
If you're new to C#, I'd really recommend learning it outside the Unity environment first - install Visual Studio 2015 Community Edition and learn about the basics of the language via console apps etc, with the aid of a good book. You'll be in a much simpler environment to experiment with, and you won't constantly be wondering whether odd behaviour is due to C# or Unity.
I am currently working on a project in C#. I am syncing access to a state variable using a single lock. This state variable is triggered to be set for a given period of time and then should have its value reset. My current code is as follows.
using System.Threading;
class Test
{
object syncObj = new object();
bool state = false;
Timer stateTimer;
Test()
{
stateTimer = new Timer(ResetState, this, Timeout.Infinite, Timeout.Infinite);
}
void SetState()
{
lock(syncObj)
{
state = true;
stateTimer.Change(1000, Timeout.Infinite);
}
}
static void ResetState(object o)
{
Test t = o as Test;
lock(t.syncObj)
{
t.state = false;
}
}
}
Given that it is valid to call SetState again before ResetState is called by the Timer (i.e. it is allowed to extend the period of time that state is true), I can imagine situations where a single lock may not be enough. The specific case I'm thinking of is this
Both SetState and ResetState are entered at the same time, on the main thread and the Timer thread respectively
SetState acquires the lock first and correctly sets state to true and triggers the timer to start again
ResetState then incorrectly sets state to false meaning that state is not true for the expected period of time
I've been scratching my head over this one for a little while. The closest I got to being able to solve it was by using two locks but in the end I found this caused other issues (at least, the way I'd done it).
Is there a known way to solve this problem (and should I be reading something to refresh my knowledge of synchronisation)?
UPDATE: I forgot to mention that the current state of the timer cannot be queried in this instance. If it could I would imagine checking the remaining time in ResetState to determine that the timer is really stopped.
First and foremost: it's a bad idea to expose the locking object publicly!
class Test
{
private object syncObj = new object();
private bool state = false;
private Timer stateTimer;
public Test()
{
stateTimer = new Timer(ResetState, this, Timeout.Infinite, Timeout.Infinite);
}
public void SetState()
{
lock(syncObj)
{
state = true;
stateTimer.Change(1000, Timeout.Infinite);
}
}
public static void ResetState(object o)
{
Test t = o as Test;
t.ResetState();
}
Since you're no longer exposing the locking object, you'll have to create another method to reset the state:
public void ResetState()
{
lock(syncObj)
{
state = false;
stateTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
}
Note that we also take care of another problem in the new ResetState method and that is to force the timer not to fire again. This will only guarantee that the state flag will not be in out of sync with the timer; i.e. if you set the state, it will remain set for the expected amount of time or until the reset method is called.
Update
If you want to reject the reset attempt, then make the state variable an enum:
enum EState
{
Off = 0,
On = 1,
Waiting = 2
}
private EState state = EState.Off;
// Provide a state property to check if the state is on or of (waiting is considered to be Off)
public bool State{ get{ return state == EState.On;} }
In addition, you will now need to modify the SetState method and you will need two reset methods (the private one will be used with by the timer).
public void SetState()
{
lock(syncObj)
{
state = EState.Waiting;
stateTimer.Change(1000, Timeout.Infinite);
}
}
public void ResetState()
{
lock(syncObj)
{
if(state != EState.Waiting)
{
state = EState.Off;
}
}
}
private void TimerResetState()
{
lock(syncObj)
{
state = EState.Off;
stateTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
So now your constructor will look like this:
public Test()
{
stateTimer = new Timer(TimerResetState, this, Timeout.Infinite, Timeout.Infinite);
}
Things should work roughly along those lines.