using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Pfz.RemoteGaming.Internal;
using Pfz.Threading;
namespace Pfz.RemoteGaming
{
/// <summary>
/// This class represents a Room in the server game.
/// Changes to remote game properties are seen by everyone that is in the same room.
/// Usually games start with exclusive rooms (so only client and server comunicate) and then
/// the Participant joins another (common) room.
/// </summary>
public class RemoteGameRoom:
ThreadSafeExceptionAwareDisposable
{
#region Fields
internal static long _idGenerator;
internal HashSet<RemoteGameComponent> _addedComponents = new HashSet<RemoteGameComponent>();
internal HashSet<RemoteGameComponent> _removedComponents = new HashSet<RemoteGameComponent>();
internal Dictionary<long, RemoteGameComponent> _components = new Dictionary<long, RemoteGameComponent>();
internal bool _started;
private IEnumerator<bool> _animation;
private HighPrecisionTimer _timer;
internal static long _participantIdGenerator;
#endregion
#region Constructor
/// <summary>
/// Creates a new room.
/// </summary>
public RemoteGameRoom()
{
MustDisposeIfEmpty = true;
}
#endregion
#region Dispose
/// <summary>
/// Disposes the actual room.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
Disposer.Dispose(ref _timer);
Disposer.Dispose(ref _animation);
var components = _components;
if (components != null)
{
foreach(var component in components.Values)
component.Dispose();
_components = null;
}
_addedComponents = null;
_removedComponents = null;
}
base.Dispose(disposing);
}
#endregion
#region Properties
#region _RoomLock
internal object _RoomLock
{
get
{
return base.DisposeLock;
}
}
#endregion
#region MustDisposeIfEmpty
/// <summary>
/// Gets or sets a value telling if the room must be disposed if there are no Participants in it.
/// The default is true.
/// </summary>
public bool MustDisposeIfEmpty { get; set; }
#endregion
#region ParticipantCount
/// <summary>
/// Gets the number of participants in this room.
/// </summary>
public int ParticipantCount
{
get
{
lock(DisposeLock)
{
CheckUndisposed();
return _participants.Count;
}
}
}
#endregion
#region UpdateTime
/// <summary>
/// Gets or sets the interval time used between frames.
/// A value of 0 means that there will be no updates.
/// </summary>
public TimeSpan UpdateTime
{
get
{
var timer = _timer;
if (timer == null)
return TimeSpan.Zero;
return timer.Interval;
}
}
#endregion
#endregion
#region Methods
#region CheckUndisposed
internal new void CheckUndisposed()
{
base.CheckUndisposed();
}
#endregion
#region _CreateComponent
internal T _CreateComponent<T>(RemoteGameParticipant owner)
where
T: RemoteGameComponent
{
var result = (T)_AssemblyGenerator.GetConstructor(typeof(T)).Invoke(null);
try
{
result._roomLock = DisposeLock;
lock(_RoomLock)
{
CheckUndisposed();
result._room = this;
long id = Interlocked.Increment(ref _idGenerator);
result._id = id;
_addedComponents.Add(result);
_components.Add(id, result);
result.IsPublic = true;
if (owner != null)
result.Owner = owner;
result.OnInitialize();
}
}
catch
{
result.Dispose();
throw;
}
return result;
}
#endregion
#region _RemoveComponent
internal void _RemoveComponent(RemoteGameComponent component)
{
lock(_RoomLock)
{
if (WasDisposed)
return;
if (!_addedComponents.Remove(component))
_removedComponents.Add(component);
_components.Remove(component._id);
}
}
#endregion
#region _ApplyChangesImmediately
private void _ApplyChangesImmediately()
{
try
{
_ApplyChangesImmediately(true);
}
catch(Exception exception)
{
Dispose(exception);
}
}
private void _ApplyChangesImmediately(bool updateAnimation)
{
RemoteGameParticipant[] participants;
RemoteGameComponent[] components;
RemoteGameComponent[] addedComponents;
RemoteGameComponent[] removedComponents;
IEnumerator<bool> animation;
lock(_RoomLock)
{
if (WasDisposed)
return;
if (_participants.Count == 0)
{
if (MustDisposeIfEmpty)
{
Dispose();
return;
}
foreach (var component in _components.Values)
{
component._componentLock.EnterWriteLock();
try
{
if (!component._wasDisposed)
component._changes.Clear();
}
finally
{
component._componentLock.ExitWriteLock();
}
}
return;
}
animation = _animation;
participants = _participants.ToArray();
components = _components.Values.ToArray();
addedComponents = _addedComponents.ToArray();
removedComponents = _removedComponents.ToArray();
_addedComponents.Clear();
_removedComponents.Clear();
}
if (updateAnimation && animation != null && !animation.MoveNext())
{
Dispose();
return;
}
var volatileValues = new List<KeyValuePair<long, object[]>>();
var componentChanges = new List<KeyValuePair<RemoteGameComponent, KeyValuePair<int, object>[]>>();
foreach (var component in components)
{
var changes = component._GetChanges();
if (changes != null)
{
var pair = new KeyValuePair<RemoteGameComponent, KeyValuePair<int, object>[]>(component, changes);
componentChanges.Add(pair);
}
component._AddVolatileValues(volatileValues);
}
// we must call StoreChanges even if there are none, as this allows some participants that had their notifications
// disabled or with private components to work properly.
foreach (var participant in participants)
participant._StoreChanges(componentChanges, addedComponents, removedComponents, volatileValues);
}
#endregion
#region ApplyChangesImmediately
/// <summary>
/// Applies any changes done to this room immediately.
/// You must call this method if the room doesn't have an animation.
/// </summary>
public void ApplyChangesImmediately()
{
_ApplyChangesImmediately(false);
}
#endregion
#region CreateComponent
/// <summary>
/// Creates a component into this room that is not owned by any participant and is public.
/// </summary>
public T CreateComponent<T>()
where
T: RemoteGameComponent
{
return _CreateComponent<T>(null);
}
#endregion
#region GetComponents
/// <summary>
/// Gets all components in this room.
/// Note that private components are only acessible by the Participant.GetPrivateComponents.
/// </summary>
/// <returns>An array with the components or null if this room was disposed.</returns>
public RemoteGameComponent[] GetComponents()
{
lock(_RoomLock)
{
if (WasDisposed)
return null;
return _components.Values.ToArray();
}
}
#endregion
#region GetParticipants
internal HashSet<RemoteGameParticipant> _participants = new HashSet<RemoteGameParticipant>();
/// <summary>
/// Gets all participants in this room.
/// </summary>
/// <returns>An array with the participants or null if this room was disposed.</returns>
public RemoteGameParticipant[] GetParticipants()
{
lock(_RoomLock)
{
if (WasDisposed)
return null;
return _participants.ToArray();
}
}
#endregion
#region Start
#region Start()
/// <summary>
/// Starts this run without an animation.
/// Use it for rooms that respond to requests only.
/// </summary>
public void Start()
{
lock(_RoomLock)
{
CheckUndisposed();
if (_started)
throw new RemoteGameException("RemoteGameRoom already started.");
_started = true;
}
ApplyChangesImmediately();
}
#endregion
#region Start(double interval, IEnumerator<bool> animation)
/// <summary>
/// Starts this room, which will run the given animation in the given interval times.
/// </summary>
public void Start(double interval, IEnumerator<bool> animation)
{
if (animation == null)
throw new ArgumentNullException("animation");
lock(_RoomLock)
{
CheckUndisposed();
if (_started)
throw new RemoteGameException("RemoteGameRoom already started.");
_started = true;
_animation = animation;
_timer = new HighPrecisionTimer(_ApplyChangesImmediately, interval);
}
}
#endregion
#endregion
#region RegisterAction - Many overloads
internal Dictionary<Type, Func<RemoteGameParticipant, RemoteGameRequest, object>> _registeredActions = new Dictionary<Type, Func<RemoteGameParticipant, RemoteGameRequest, object>>();
/// <summary>
/// Registers the action to be taken by a given request type.
/// </summary>
public IDisposable RegisterAction<T>(Func<RemoteGameParticipant, T, object> action)
where
T: RemoteGameRequest
{
if (action == null)
throw new ArgumentNullException("action");
var registeredAction =
new Func<RemoteGameParticipant, RemoteGameRequest, object>
(
(participant, request) => action(participant, (T)request)
);
lock(_registeredActions)
_registeredActions.Add(typeof(T), registeredAction);
return new _UnregisterAction(this, typeof(T));
}
/// <summary>
/// Registers the action to be taken by a given request type.
/// </summary>
public IDisposable RegisterAction<T>(Func<object> action)
where
T: RemoteGameRequest
{
if (action == null)
throw new ArgumentNullException("action");
var registeredAction =
new Func<RemoteGameParticipant, RemoteGameRequest, object>
(
(participant, request) => action()
);
lock(_registeredActions)
_registeredActions.Add(typeof(T), registeredAction);
return new _UnregisterAction(this, typeof(T));
}
/// <summary>
/// Registers the action to be taken by a given request type.
/// </summary>
public IDisposable RegisterAction<T>(Func<T, object> action)
where
T: RemoteGameRequest
{
if (action == null)
throw new ArgumentNullException("action");
var registeredAction =
new Func<RemoteGameParticipant, RemoteGameRequest, object>
(
(participant, request) => action((T)request)
);
lock(_registeredActions)
_registeredActions.Add(typeof(T), registeredAction);
return new _UnregisterAction(this, typeof(T));
}
/// <summary>
/// Registers the action to be taken by a given request type.
/// </summary>
public IDisposable RegisterAction<T>(Func<RemoteGameParticipant, object> action)
where
T: RemoteGameRequest
{
if (action == null)
throw new ArgumentNullException("action");
var registeredAction =
new Func<RemoteGameParticipant, RemoteGameRequest, object>
(
(participant, request) => action(participant)
);
lock(_registeredActions)
_registeredActions.Add(typeof(T), registeredAction);
return new _UnregisterAction(this, typeof(T));
}
#endregion
#endregion
}
}