basic Sync checking. No protocol stuff yet; just checks that input events are well-behaved.

This commit is contained in:
Bob
2010-01-09 14:25:54 +13:00
parent 1bf7e462d6
commit b24983f0ef
41 changed files with 147 additions and 24 deletions

View File

@@ -46,5 +46,10 @@ namespace OpenRa
if( i.Value is T )
yield return (T)i.Value;
}
public IEnumerator<object> GetEnumerator()
{
return WithInterface<object>().GetEnumerator();
}
}
}

View File

@@ -10,12 +10,16 @@ namespace OpenRa.Game
{
class Actor
{
[Sync]
public readonly TypeDictionary traits = new TypeDictionary();
public readonly UnitInfo Info;
public readonly uint ActorID;
[Sync]
public int2 Location;
[Sync]
public Player Owner;
[Sync]
public int Health;
IActivity currentActivity;

View File

@@ -146,15 +146,25 @@ namespace OpenRa.Game
public float2 MousePosition { get { return dragEnd; } }
public Cursor ChooseCursor()
{
int sync = Game.world.SyncHash();
try
{
var mi = new MouseInput
{
Location = (Game.CellSize * MousePosition - Game.viewport.Location).ToInt2(),
Location = ( Game.CellSize * MousePosition - Game.viewport.Location ).ToInt2(),
Button = MouseButton.Right,
Modifiers = GetModifierKeys(),
};
return orderGenerator.GetCursor(MousePosition.ToInt2(), mi);
return orderGenerator.GetCursor( MousePosition.ToInt2(), mi );
}
finally
{
if( sync != Game.world.SyncHash() )
throw new InvalidOperationException( "Desync in Controller.ChooseCursor" );
}
}
Cache<int, List<Actor>> controlGroups = new Cache<int, List<Actor>>(_ => new List<Actor>());

0
OpenRa.Game/Effects/Bullet.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/Corpse.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/DelayedAction.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/Explosion.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/FlashTarget.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/GpsSatellite.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/IEffect.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/InvulnEffect.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/Missile.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/MoveFlash.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/PowerDownIndicator.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/RepairIndicator.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/SatelliteLaunch.cs Normal file → Executable file
View File

0
OpenRa.Game/Effects/Smoke.cs Normal file → Executable file
View File

View File

@@ -95,6 +95,8 @@ namespace OpenRa.Game
void DispatchMouseInput(MouseInputEvent ev, MouseEventArgs e)
{
int sync = Game.world.SyncHash();
Game.viewport.DispatchMouseInput(
new MouseInput
{
@@ -103,6 +105,9 @@ namespace OpenRa.Game
Location = new int2(e.Location),
Modifiers = (Modifiers)(int)ModifierKeys,
});
if( sync != Game.world.SyncHash() )
throw new InvalidOperationException( "Desync in DispatchMouseInput" );
}
protected override void OnMouseDown(MouseEventArgs e)
@@ -136,6 +141,8 @@ namespace OpenRa.Game
{
base.OnKeyDown(e);
int sync = Game.world.SyncHash();
/* hack hack hack */
if (e.KeyCode == Keys.F8 && !Game.orderManager.GameStarted)
{
@@ -156,16 +163,24 @@ namespace OpenRa.Game
if (!Game.chat.isChatting)
if (e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9)
Game.controller.DoControlGroup( (int)e.KeyCode - (int)Keys.D0, (Modifiers)(int)e.Modifiers );
if( sync != Game.world.SyncHash() )
throw new InvalidOperationException( "Desync in OnKeyDown" );
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
int sync = Game.world.SyncHash();
if (e.KeyChar == '\r')
Game.chat.Toggle();
else if (Game.chat.isChatting)
Game.chat.TypeChar(e.KeyChar);
if( sync != Game.world.SyncHash() )
throw new InvalidOperationException( "Desync in OnKeyPress" );
}
}

View File

@@ -129,6 +129,7 @@
<Compile Include="SupportPowers\NullPower.cs" />
<Compile Include="Support\Stopwatch.cs" />
<Compile Include="Support\PerfHistory.cs" />
<Compile Include="Sync.cs" />
<Compile Include="Traits\AcceptsOre.cs" />
<Compile Include="Traits\Activities\Attack.cs" />
<Compile Include="Traits\Activities\CaptureBuilding.cs" />

57
OpenRa.Game/Sync.cs Executable file
View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace OpenRa.Game
{
class SyncAttribute : Attribute { }
static class Sync
{
public static int CalculateSyncHash( object obj )
{
int hash = 0; // TODO: start with a more interesting initial value.
// TODO: cache the Syncable fields; maybe use DynamicMethod to make this fast?
// FIXME: does GetFields even give fields in a well-defined order?
const BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
foreach( var field in obj.GetType().GetFields( bf ).Where( x => x.GetCustomAttributes( typeof( SyncAttribute ), true ).Length != 0 ) )
{
if( field.FieldType == typeof( int ) )
hash ^= (int)field.GetValue( obj );
else if( field.FieldType == typeof( Actor ) )
{
var a = (Actor)field.GetValue( obj );
if( a != null )
hash ^= (int)( a.ActorID << 16 );
}
else if( field.FieldType == typeof( TypeDictionary ) )
{
foreach( var o in (TypeDictionary)field.GetValue( obj ) )
hash += CalculateSyncHash( o );
}
else if( field.FieldType == typeof( bool ) )
hash ^= (bool)field.GetValue( obj ) ? 0xaaa : 0x555;
else if( field.FieldType == typeof( int2 ) )
{
var i2 = (int2)field.GetValue( obj );
hash ^= ( ( i2.X * 5 ) ^ ( i2.Y * 3 ) ) / 4;
}
else if( field.FieldType == typeof( Player ) )
{
var p = (Player)field.GetValue( obj );
if( p != null )
hash ^= p.Index * 0x567;
}
else
throw new NotImplementedException( "SyncAttribute on unhashable field" );
}
return hash;
}
}
}

View File

@@ -8,10 +8,12 @@ namespace OpenRa.Game.Traits
{
class AttackBase : IIssueOrder, IResolveOrder, ITick
{
public Actor target;
[Sync] public Actor target;
// time (in frames) until each weapon can fire again.
[Sync]
protected int primaryFireDelay = 0;
[Sync]
protected int secondaryFireDelay = 0;
int primaryBurst;

View File

@@ -7,7 +7,7 @@ namespace OpenRa.Game.Traits
public AttackFrontal(Actor self, int facingTolerance)
: base(self) { FacingTolerance = facingTolerance; }
int FacingTolerance;
readonly int FacingTolerance;
public override void Tick(Actor self)
{

View File

@@ -13,7 +13,9 @@ namespace OpenRa.Game.Traits
{
readonly Actor self;
public readonly BuildingInfo unitInfo;
[Sync]
bool isRepairing = false;
[Sync]
bool manuallyDisabled = false;
public bool ManuallyDisabled { get { return manuallyDisabled; } }
public bool Disabled { get { return (manuallyDisabled || (unitInfo.Powered && self.Owner.GetPowerState() != PowerState.Normal)); } }

View File

@@ -7,8 +7,9 @@ namespace OpenRa.Game.Traits
class ChronoshiftDeploy : IIssueOrder, IResolveOrder, ISpeedModifier, ITick, IPips
{
// Recharge logic
[Sync]
int chargeTick = 0; // How long until we can chronoshift again?
int chargeLength = (int)(Rules.Aftermath.ChronoTankDuration * 60 * 25); // How long between shifts?
readonly int chargeLength = (int)(Rules.Aftermath.ChronoTankDuration * 60 * 25); // How long between shifts?
public ChronoshiftDeploy(Actor self) { }

View File

@@ -8,7 +8,9 @@ namespace OpenRa.Game.Traits
class Chronoshiftable : IResolveOrder, ISpeedModifier, ITick
{
// Return-to-sender logic
[Sync]
int2 chronoshiftOrigin;
[Sync]
int chronoshiftReturnTicks = 0;
public Chronoshiftable(Actor self) { }

View File

@@ -6,6 +6,7 @@ namespace OpenRa.Game.Traits
{
class Cloak : IRenderModifier, INotifyAttack, ITick
{
[Sync]
int remainingUncloakTime = 2; /* setup for initial cloak */
public Cloak(Actor self) {}

View File

@@ -5,7 +5,9 @@ namespace OpenRa.Game.Traits
{
class Harvester : IIssueOrder, IResolveOrder, IPips
{
[Sync]
public int oreCarried = 0; /* sum of these must not exceed capacity */
[Sync]
public int gemsCarried = 0;
public bool IsFull { get { return oreCarried + gemsCarried == Rules.General.BailCount; } }

View File

@@ -5,6 +5,7 @@ namespace OpenRa.Game.Traits
{
class IronCurtainable : IResolveOrder, IDamageModifier, ITick
{
[Sync]
int RemainingTicks = 0;
public IronCurtainable(Actor self) { }

View File

@@ -4,6 +4,7 @@ namespace OpenRa.Game.Traits
{
class LimitedAmmo : INotifyAttack, IPips
{
[Sync]
int ammo;
Actor self;

View File

@@ -9,6 +9,7 @@ namespace OpenRa.Game.Traits
{
readonly Actor self;
[Sync]
int2 __fromCell;
public int2 fromCell
{

View File

@@ -74,6 +74,7 @@ namespace OpenRa.Game.Traits
}
// Key: Production category.
// TODO: sync this
readonly Cache<string, List<ProductionItem>> production
= new Cache<string, List<ProductionItem>>( _ => new List<ProductionItem>() );

View File

@@ -6,6 +6,7 @@ namespace OpenRa.Game.Traits
{
class RallyPoint : IRender, IIssueOrder, IResolveOrder, ITick
{
[Sync]
public int2 rallyPoint;
public Animation anim;

View File

@@ -7,7 +7,9 @@ namespace OpenRa.Game.Traits
class RenderWarFactory : IRender, INotifyBuildComplete, INotifyDamage, ITick, INotifyProduction
{
public Animation roof;
[Sync]
bool doneBuilding;
[Sync]
bool isOpen;
public readonly Actor self;

View File

@@ -6,6 +6,7 @@ namespace OpenRa.Game.Traits
{
class Submarine : IRenderModifier, INotifyAttack, ITick, INotifyDamage
{
[Sync]
int remainingSurfaceTime = 2; /* setup for initial dive */
public Submarine(Actor self) { }

View File

@@ -8,6 +8,7 @@ namespace OpenRa.Game.Traits
const float proneDamage = .5f;
const float proneSpeed = .5f;
[Sync]
int remainingProneTime = 0;
public bool IsProne { get { return remainingProneTime > 0; } }

View File

@@ -13,15 +13,15 @@ namespace OpenRa.Game.Traits
interface ITick { void Tick(Actor self); }
interface IRender { IEnumerable<Renderable> Render(Actor self); }
interface IIssueOrder { Order IssueOrder( Actor self, int2 xy, MouseInput mi, Actor underCursor ); }
interface IResolveOrder { void ResolveOrder( Actor self, Order order ); }
interface INotifySold { void Sold(Actor self); }
interface INotifyDamage { void Damaged(Actor self, AttackInfo e); }
interface INotifyBuildComplete { void BuildingComplete (Actor self); }
interface INotifyProduction { void UnitProduced(Actor self, Actor other); }
interface IAcceptThief { void OnSteal(Actor self, Actor thief); }
interface IIssueOrder { Order IssueOrder(Actor self, int2 xy, MouseInput mi, Actor underCursor); }
interface IResolveOrder { void ResolveOrder(Actor self, Order order); }
interface IProducer
{
bool Produce( Actor self, UnitInfo producee );

View File

@@ -3,6 +3,7 @@ namespace OpenRa.Game.Traits
{
class Turreted : ITick
{
[Sync]
public int turretFacing = 0;
public int? desiredFacing;

View File

@@ -3,7 +3,9 @@ namespace OpenRa.Game.Traits
{
class Unit : INotifyDamage
{
[Sync]
public int Facing;
[Sync]
public int Altitude;
public Unit( Actor self ) { }

View File

@@ -52,5 +52,14 @@ namespace OpenRa.Game
{
return nextAID++;
}
public int SyncHash()
{
int ret = 0;
foreach( var a in Actors )
ret += (int)a.ActorID * Sync.CalculateSyncHash( a );
return ret;
}
}
}