public async Task<bool> MoveAsync(Point sourcePosition, Point targetPosition) { var source = await Map.GetAsync(sourcePosition); var target = await Map.GetAsync(targetPosition); var initiator = new MoveInitiator(source.Tile.Entity, sourcePosition); var transaction = new TransactionWithMoveSupport(initiator); var result = await Map.MoveAsync(sourcePosition, targetPosition, transaction); var request = new RemoteMoveRequest( new VersionedPoint(sourcePosition, source.Version), new VersionedPoint(targetPosition, target.Version), result.Transaction.Changes.Select(c => new VersionedPoint(c.Key, c.Value.Version))); var remoteMoveResult = await _serverStub.MoveAsync(request, PlayerInfo.PlayerId); if (remoteMoveResult.IsSuccessful) { // Commit transaction await transaction.CommitAsync(Map, remoteMoveResult.NewVersion); AnalyzeEvents(transaction.Events); return true; } else { // Our map was not up to date => apply updated chunks foreach (var kvp in remoteMoveResult.ChunkUpdates) await ApplyChunkAsync(kvp.Value, kvp.Key); } return false; }
/// <inheritdoc/> public async Task<RemoteMoveResult> MoveAsync(RemoteMoveRequest move, string playerId) { using (await _clientsLock.LockAsync()) { var connection = GetClient(playerId); await connection.MoveSemaphore.WaitAsync(); try { var source = await Map.GetAsync(move.SourcePosition); var target = await Map.GetAsync(move.TargetPosition); var initiator = new MoveInitiator(source.Tile.Entity, move.SourcePosition); var transaction = new TransactionWithMoveSupport(initiator); var moveResult = await Map.MoveAsync(move.SourcePosition, move.TargetPosition, transaction); // Compare affected tiles of the client and those of the server var clientAffectedTiles = move.AffectedPositions.Union(new[] { move.SourcePosition, move.TargetPosition }); var serverAffectedTiles = moveResult.Transaction.Changes .Select(c => new VersionedPoint(c.Key, c.Value.Version)) .Union(new[] { new VersionedPoint(move.SourcePosition, source.Version), new VersionedPoint(move.TargetPosition, target.Version) }) .ToList(); var versions = serverAffectedTiles.FullOuterJoin(clientAffectedTiles, vp => vp.Position, vp => vp.Position, (serverVP, clientVP, p) => new { Position = p, ServerVersion = serverVP.Version, ClientVersion = clientVP.Version }, VersionedPoint.Empty, VersionedPoint.Empty); if (versions.Any(v => v.ClientVersion != -1 && v.ServerVersion != -1 && v.ClientVersion > v.ServerVersion)) throw new NotImplementedException("This should not happen. Clients must not have a newer version than the server"); var conflictingVersions = versions.Where(v => (v.ClientVersion == -1 && v.ServerVersion != -1) || (v.ClientVersion != -1 && v.ServerVersion == -1) || (v.ClientVersion != -1 && v.ServerVersion != -1 && v.ClientVersion < v.ServerVersion)); if (conflictingVersions.Any()) { // Client not up to date => Cancel move request and send up-to-date chunks var chunks = await Task.WhenAll(conflictingVersions .Select(v => v.Position / Chunk.Size).Distinct() .Select(async index => new KeyValuePair<Point, IChunk>(index, await Map.ChunkLoader.GetAsync(index)))); return RemoteMoveResult.CreateFaulted(chunks); } else { // Client and server are on the same version (regarding affected tiles) // => Commit and schedule follow-up transactions // => Notify other clients about the changes var newVersion = await CommitAndBroadcastAsync(moveResult.Transaction, moveResult.FollowUpEvents, connection); return RemoteMoveResult.CreateSuccessful(newVersion); } } finally { connection.MoveSemaphore.Release(); } } }
internal override async Task OnFollowUpTransactionAsync(IFollowUpArgs e, Point position) { if (e.Initiator.Object is PistonTile) return; var trigger = (await e.GetAsync(TriggerPosition)).Tile as ITrigger; var isTriggerOn = trigger?.IsOn ?? false; var initiator = new MoveInitiator(this, position); if (!IsExtended && isTriggerOn) { // Extend piston if (await e.MoveAsync(position, position + Direction)) e.Emit(new PistonExtendRetractEvent(true)); } else if (IsExtended && !isTriggerOn) { // Retract piston if (await e.MoveAsync(position + Direction, position)) e.Emit(new PistonExtendRetractEvent(false)); } // Otherwise do nothing }
public FollowUpEvent(Point position, MoveInitiator initiator, DateTimeOffset executionTime) { Position = position; Initiator = initiator; ExecutionTime = executionTime; }