Click here to Skip to main content
15,892,746 members
Articles / Artificial Intelligence

Writing a Multiplayer Game (in WPF)

Rate me:
Please Sign up or sign in to vote.
4.93/5 (131 votes)
16 Mar 2012CPOL25 min read 215.8K   17.1K   246  
This article will explain some concepts of game development and how to apply and adapt them for multiplayer development.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using Pfz.Threading;

namespace Pfz.RemoteGaming
{
	/// <summary>
	/// Class used by the emitted RemoteGameComponent classes. You don't need to use it directly, but it must be public to be acessible by
	/// the emitted classes.
	/// </summary>
	[Serializable]
	public sealed class RemoteGameProperty:
		IEquatable<RemoteGameProperty>
	{
		#region Static Area
			#region Fields
				internal static YieldReaderWriterLockSlim _propertiesLock;
				internal static List<RemoteGameProperty> _properties = new List<RemoteGameProperty>();
			#endregion

			#region GetAllProperties
				/// <summary>
				/// Gets a copy of all available game properties.
				/// Note that such list changes everytime a RemoteGameClient connects.
				/// </summary>
				public RemoteGameProperty[] GetAllProperties()
				{
					_propertiesLock.EnterReadLock();
					try
					{
						return _properties.ToArray();
					}
					finally
					{
						_propertiesLock.ExitReadLock();
					}
				}
			#endregion

			#region GetPropertyValueById
				/// <summary>
				/// Gets the value of a property by id.
				/// </summary>
				public static T GetPropertyValueById<T>(int id, RemoteGameComponent component)
				{
					if (component == null)
						throw new ArgumentNullException("component");

					RemoteGameProperty info;
					_propertiesLock.EnterReadLock();
					try
					{
						if (id < 0 || id >= _properties.Count)
							throw new RemoteGameException("Can't find the given property by id. Is a RemoteGameClient or Listener running?");

						info = _properties[id];
					}
					finally
					{
						_propertiesLock.ExitReadLock();
					}

					object result;
					component._componentLock.EnterReadLock();
					try
					{
						component.CheckUndisposed();

						if (component._values.TryGetValue(id, out result))
							return (T)result;
					}
					finally
					{
						component._componentLock.ExitReadLock();
					}

					return default(T);
				}
			#endregion
			#region SetPropertyValueById
				/// <summary>
				/// Sets the value of a property by id.
				/// </summary>
				public static void SetPropertyValueById<T>(int id, RemoteGameComponent component, T value)
				{
					object valueAsObject = value;
					if (object.Equals(value, default(T)))
					{
						_SetPropertyValueById(id, component, null, component.IsClientComponent);
						valueAsObject = null;
					}
					else
						_SetPropertyValueById(id, component, value, component.IsClientComponent);

					var client = component._client;
					if (client != null)
						client._GenerateNotificationIfNeeded();
				}
				internal static void _SetPropertyValueById(int id, RemoteGameComponent component, object value, bool setByClient)
				{
					if (component == null)
						throw new ArgumentNullException("component");

					RemoteGameProperty info;
					_propertiesLock.EnterReadLock();
					try
					{
						if (id < 0 || id >= _properties.Count)
							throw new RemoteGameException("Can't find the given property by id. Is a RemoteGameClient or Listener running?");

						info = _properties[id];
					}
					finally
					{
						_propertiesLock.ExitReadLock();
					}

					if (info._isClientProperty != setByClient && !component._isParticipantDisconnected)
						throw new RemoteGameException("A client property is set by the server or vice-versa.");

					object oldValueObject;
					component._componentLock.EnterWriteLock();
					try
					{
						component.CheckUndisposed();

						component._values.TryGetValue(id, out oldValueObject);

						if (object.Equals(oldValueObject, value))
							return;

						if (value == null)
							component._values.Remove(id);
						else
							component._values[id] = value;

						if (!info._isVolatile)
							component._changes[id] = value;
					}
					finally
					{
						component._componentLock.ExitWriteLock();
					}
				}
			#endregion

			#region _GetVolatileProperties
				private static YieldReaderWriterLockSlim _volatilePropertiesLock;
				private static Dictionary<Type, RemoteGameProperty[]> _volatileProperties = new Dictionary<Type, RemoteGameProperty[]>();
				internal static RemoteGameProperty[] _GetVolatileProperties(Type type)
				{
					RemoteGameProperty[] result;
					_volatilePropertiesLock.EnterReadLock();
					try
					{
						_volatileProperties.TryGetValue(type, out result);
					}
					finally
					{
						_volatilePropertiesLock.ExitReadLock();
					}

					if (result != null)
						return result;

					_volatilePropertiesLock.EnterUpgradeableLock();
					bool upgraded = false;
					try
					{
						if (_volatileProperties.TryGetValue(type, out result))
							return result;

						var volatileProperties = new List<RemoteGameProperty>();

						_propertiesLock.EnterReadLock();
						try
						{
							foreach(var property in _properties)
								if (property._isVolatile)
									if (type.IsSubclassOf(property._declaringType))
										volatileProperties.Add(property);
						}
						finally
						{
							_propertiesLock.ExitReadLock();
						}

						result = volatileProperties.ToArray();

						_volatilePropertiesLock.UpgradeToWriteLock();
						upgraded = true;
						_volatileProperties.Add(type, result);
					}
					finally
					{
						if (upgraded)
							_volatilePropertiesLock.ExitUpgradedLock();
						else
							_volatilePropertiesLock.ExitUpgradeableLock();
					}

					return result;
				}
			#endregion
			#region GetVolatileProperties
				/// <summary>
				/// Gets a collection with all volatile game properties found on the given type.
				/// </summary>
				public static ReadOnlyCollection<RemoteGameProperty> GetVolatileProperties(Type type)
				{
					if (type == null)
						throw new ArgumentNullException("type");

					if (!typeof(RemoteGameComponent).IsAssignableFrom(type))
						throw new ArgumentException("type must be a RemoteGameComponent.", "type");

					var volatileProperties = _GetVolatileProperties(type);
					var readOnlyProperties = new ReadOnlyCollection<RemoteGameProperty>(volatileProperties);
					return readOnlyProperties;
				}
			#endregion
		#endregion

		#region Constructor
			internal RemoteGameProperty(int id, Type declaringType, Type propertyType, string name, bool isClientProperty, bool isVolatile)
			{
				_id = id;
				_declaringType = declaringType;
				_propertyType = propertyType;
				_name = name;
				_isClientProperty = isClientProperty;
				_isVolatile = isVolatile;
			}
		#endregion

		#region Properties
			#region Id
				internal readonly int _id;

				/// <summary>
				/// Gets the Id of this property. Such Id is unique between all game-properties, independent of the class.
				/// </summary>
				public int Id
				{
					get
					{
						return _id;
					}
				}
			#endregion
			#region DeclaringType
				internal readonly Type _declaringType;

				/// <summary>
				/// Gets the type that declares this game property.
				/// </summary>
				public Type DeclaringType
				{
					get
					{
						return _declaringType;
					}
				}
			#endregion
			#region PropertyType
				internal readonly Type _propertyType;

				/// <summary>
				/// Gets the type of the property.
				/// </summary>
				public Type PropertyType
				{
					get
					{
						return _propertyType;
					}
				}
			#endregion
			#region Name
				internal readonly string _name;

				/// <summary>
				/// Gets the name of the property.
				/// </summary>
				public string Name
				{
					get
					{
						return _name;
					}
				}
			#endregion
			#region IsClientProperty
				internal readonly bool  _isClientProperty;

				/// <summary>
				/// Gets a value indicating if this property is changeable from the client side.
				/// </summary>
				public bool IsClientProperty
				{
					get
					{
						return _isClientProperty;
					}
				}
			#endregion
			#region IsVolatile
				internal readonly bool _isVolatile;

				/// <summary>
				/// Gets a value indicating if this property is volatile.
				/// Volatile game properties are sent again each frame, so a lost frame doesn't cause a delay until
				/// it is received, as it can be discarded.
				/// </summary>
				public bool IsVolatile
				{
					get
					{
						return _isVolatile;
					}
				}
			#endregion
		#endregion
		#region Methods
			#region Equals
				/// <summary>
				/// Compares this property info with another one.
				/// </summary>
				public bool Equals(RemoteGameProperty other)
				{
					if (other == null)
						return false;

					return _id == other._id;
				}

				/// <summary>
				/// Compares this property info with another object.
				/// </summary>
				public override bool Equals(object obj)
				{
					var other = obj as RemoteGameProperty;
					return Equals(other);
				}
			#endregion
			#region GetHashCode
				/// <summary>
				/// Gets the Id of this property as the hashcode.
				/// </summary>
				public override int GetHashCode()
				{
					return _id;
				}
			#endregion

			#region GetPropertyInfo
				/// <summary>
				/// Tries to get a real property-info based on this game property info.
				/// </summary>
				public PropertyInfo GetPropertyInfo()
				{
					var result = _declaringType.GetProperty(_name);

					if (result == null)
						throw new RemoteGameException("Can't find property " + _name + " in type " + _declaringType.FullName);

					return result;
				}
			#endregion
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions