Serveur de jeu sur MS Orleans - Partie 1: Que sont les acteurs



Salut Habr! Et donc, le quatorzième jour de la lance, j'ai décidé de commencer à créer un serveur de jeu simple pour un simple tir en ligne. Pour un sujet de l'informatique distribuée à toucher. Dans cet article introductif du cycle, je veux dire quels sont les acteurs (à Orléans, ils sont appelés grains) et le principe de leur travail. Pour cela, je suis toujours un exemple d'une application simple avec des acteurs faits maison sans Orléans. Comme on dit avant de construire un navire, voyons comment il flotte et pourquoi flotte un bateau en papier ordinaire. Pour plus de détails, bienvenue au chat.

Contenu


  • Serveur de jeu sur MS Orleans - Partie 1: Que sont les acteurs


Acteurs


Un acteur est une entité de calcul qui, en réponse à un message reçu, peut simultanément:
envoyer un nombre fini de messages à d'autres acteurs;
créer un nombre fini de nouveaux acteurs;
Sélectionnez le comportement qui sera utilisé lors du traitement du prochain message reçu.
Il n'est pas supposé qu'une certaine séquence des actions ci-dessus existe, et toutes peuvent être effectuées en parallèle.
Nous envoyons à l'acteur des messages (équipes ou événements) et lui-même peut envoyer aux autres les mêmes messages. Les coroutines à canaux fonctionnent sur un principe similaire. Ils reçoivent des données du canal et envoient des données au canal tout en travaillant de manière asynchrone.

Exemple


En fait, le code pour deux acteurs très simples:
    class Program
    {
        interface IMessage { }

        class IncrementCommand : IMessage { }

        class TellCountCommand : IMessage { }

        class SaidCountEvent : IMessage
        {
            public int Count { get; }
            public int ActorId { get; }

            public SaidCountEvent(int count, int actorId)
            {
                Count = count;
                ActorId = actorId;
            }
        }

        class WriteMessageCommand : IMessage
        {
            public string Message { get; }

            public WriteMessageCommand(string message)
            {
                Message = message;
            }
        }

        static Task CreateCounterActor(
            BlockingCollection<IMessage> output,
            BlockingCollection<IMessage> input,
            int id
            )
        {
            return Task.Run(() =>
             {
                 var count = 0;

                 while (true)
                 {
                     var m = input.Take();
                     if (m is IncrementCommand)
                         count++;
                     if (m is TellCountCommand)
                         output.Add(new SaidCountEvent(count, id));
                 }
             });
        }

        static Task CreateWriterActor(BlockingCollection<IMessage> input)
        {
            return Task.Run(() =>
             {
                 while (true)
                 {
                     var m = input.Take();
                     if (m is WriteMessageCommand write)
                         Console.WriteLine(write.Message);
                     if (m is SaidCountEvent sc)
                         Console.WriteLine(
                             $"Counter   {sc.Count}   {sc.ActorId}"
                             );
                 }
             });
        }

        static void Main(string[] args)
        {
            var writerInput = new BlockingCollection<IMessage>();
            var firstInput = new BlockingCollection<IMessage>();
            var secondInput = new BlockingCollection<IMessage>();
            var writer = CreateWriterActor(writerInput);
            var firstCounter = CreateCounterActor(writerInput, firstInput, 1);
            var secondCounter = CreateCounterActor(writerInput, secondInput, 2);
            for (int i = 0; i < 5; i++)
            {
                firstInput.Add(new IncrementCommand());
            }
            for (int i = 0; i < 9; i++)
            {
                secondInput.Add(new IncrementCommand());
            }
            firstInput.Add(new TellCountCommand());
            secondInput.Add(new TellCountCommand());
            writerInput.Add(new WriteMessageCommand("  Main"));
            Console.ReadLine();
        }
    }


  1. L'interface par laquelle nous marquons les messages que l'acteur reçoit ou envoie:
    interface IMessage { }
    

  2. La commande avec laquelle on dit à l'acteur d'augmenter son compteur (état interne):
    class IncrementCommand : IMessage { }
    

  3. La commande à l'aide de laquelle nous demandons à l'acteur de dire son état actuel (contre) aux autres:
    class TellCountCommand : IMessage { }
    

  4. Un événement qui raconte aux autres acteurs que l'acteur a raconté à tout le monde son état actuel (compteur). Généré lorsque la commande TellCountCommand est traitée:
    class SaidCountEvent : IMessage
    {
        public int Count { get; }
        public int ActorId { get; }
    
        public SaidCountEvent(int count, int actorId)
        {
            Count = count;
            ActorId = actorId;
        }
    }
    

    Le nombre est combien est désormais composé sur le compteur d'un acteur avec un identifiant égal à ActorId
  5. Cette commande indique à l'acteur d'imprimer ce message sur la console:
    class WriteMessageCommand : IMessage
    {
        public string Message { get; }
    
        public WriteMessageCommand(string message)
        {
            Message = message;
        }
    }
    

  6. Démarre une instance d'acteur qui contrôle l'état actuel du compteur:
            static Task CreateCounterActor(
             //   
                BlockingCollection<IMessage> output,
              // 
                BlockingCollection<IMessage> input,
             //              
                int id
                )
            {
                return Task.Run(() =>
                 { 
                     //  .    
                     var count = 0;
    
                     while (true)
                     {
                       //     
                      //             
                         var m = input.Take();
                         if (m is IncrementCommand)
                             count++;
                         if (m is TellCountCommand)
                             output.Add(new SaidCountEvent(count, id));
                     }
                 });
            }
    

  7. Crée un acteur qui écrit simplement des messages sur la console:
            static Task CreateWriterActor(BlockingCollection<IMessage> input)
            {
                return Task.Run(() =>
                 {
                     while (true)
                     {
                         var m = input.Take();
                         if (m is WriteMessageCommand write)
                             Console.WriteLine(write.Message);
                         if (m is SaidCountEvent sc)
                             Console.WriteLine(
                                 $"Counter   {sc.Count}   {sc.ActorId}"
                                 );
                     }
                 });
            }
    

  8.         static void Main(string[] args)
            {
                var writerInput = new BlockingCollection<IMessage>();
                var firstInput = new BlockingCollection<IMessage>();
                var secondInput = new BlockingCollection<IMessage>();
                var writer = CreateWriterActor(writerInput);
              //       
                var firstCounter = CreateCounterActor(writerInput, firstInput, 1);
                var secondCounter = CreateCounterActor(writerInput, secondInput, 2);
                //       
                for (int i = 0; i < 5; i++)
                {
                    firstInput.Add(new IncrementCommand());
                }
                //       .
                for (int i = 0; i < 9; i++)
                {
                    secondInput.Add(new IncrementCommand());
                }
                //         .
                //      writer      
                firstInput.Add(new TellCountCommand());
                secondInput.Add(new TellCountCommand());
               // writer     .
                writerInput.Add(new WriteMessageCommand("  Main"));
                Console.ReadLine();
            }
    



Eh bien, pour qu'il soit absolument magnifique et ressemble au grale (acteur) d'Orléans, faisons un emballage sur notre comptoir:
    public interface ICounterActor
    {
        void Increment();
        void TellCount();
    }

    public class CounterActor : ICounterActor
    {
        private readonly Task _taks;
        private readonly BlockingCollection<IMessage> _input;
        private readonly BlockingCollection<IMessage> _output;

        public CounterActor(BlockingCollection<IMessage> output)
        {
            _input = new BlockingCollection<IMessage>(); ;
            _output = output;
            _taks = Task.Run(() =>
            {
                //State
                var count = 0;

                while (true)
                {
                    var m = _input.Take();
                    if (m is IncrementCommand)
                        count++;
                    if (m is TellCountCommand)
                        _output.Add(new SaidCountEvent(count, _taks.Id));
                }
            });
        }

        public void Increment()
        {
            _input.Add(new IncrementCommand());
        }

        public void TellCount()
        {
            _input.Add(new TellCountCommand());
        }
    }

Ici, un modèle réactif a été utilisé. Nos acteurs ne renvoient pas de valeurs, mais génèrent uniquement de nouveaux messages avec les valeurs souhaitées. Ici, nous avons un acteur juste dans son propre fil, un fil à l'abri de tout le monde, augmente son compteur. Un autre acteur vient d'écrire sur la console. Cependant, imaginons que pour nous, chaque acteur, par exemple, calcule les coordonnées actuelles, la santé et la manne de l'un des dix joueurs qui jouent actuellement sur notre serveur. Chacun avec son propre état dans son propre flux. Nous envoyons des messages à des tiers ou recevons des messages de tiers concernant les dommages subis, la compétence utilisée ou le changement de nos coordonnées.

All Articles