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;
        }
Beispiel #2
0
        /// <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();
                }
            }
        }
Beispiel #3
0
        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;
 }