Game Server di MS Orleans - Bagian 1: Apa itu Aktor



Hai Habr! Jadi, pada hari keempat belas tombak, saya memutuskan untuk mulai membuat server gim sederhana untuk pemotretan daring sederhana. Untuk satu topik komputasi terdistribusi untuk disentuh. Dalam artikel pengantar siklus ini saya ingin memberi tahu aktor apa (di Orleans mereka disebut biji-bijian) dan prinsip kerja mereka. Untuk ini, saya masih merupakan contoh aplikasi sederhana dengan aktor buatan sendiri tanpa Orleans. Seperti yang mereka katakan sebelum membangun kapal, mari kita lihat bagaimana itu mengapung dan mengapa kapal kertas biasa mengapung. Untuk detailnya, selamat datang di kucing.

Kandungan


  • Game Server di MS Orleans - Bagian 1: Apa itu Aktor


Aktor


Seorang aktor adalah entitas komputasi yang, dalam menanggapi pesan yang diterima, dapat secara bersamaan:
mengirim sejumlah pesan ke aktor lain;
membuat sejumlah aktor baru yang terbatas;
Pilih perilaku yang akan digunakan saat memproses pesan yang diterima berikutnya.
Tidak diasumsikan bahwa urutan tertentu dari tindakan di atas ada, dan semuanya dapat dilakukan secara paralel.
Kami mengirim aktor beberapa pesan (Tim atau Acara) dan dia sendiri dapat mengirim pesan yang sama kepada orang lain. Coroutine dengan saluran bekerja berdasarkan prinsip yang sama. Mereka menerima beberapa data dari saluran dan mengirim beberapa data ke saluran saat bekerja secara tidak sinkron.

Contoh


Sebenarnya kode untuk dua aktor yang sangat sederhana:
    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. Antarmuka tempat kami menandai pesan yang diterima atau dikirim aktor:
    interface IMessage { }
    

  2. Perintah yang kami ajukan kepada aktor untuk meningkatkan lawannya (kondisi internal):
    class IncrementCommand : IMessage { }
    

  3. Perintah dengan bantuan yang kami beri tahu aktor untuk memberitahu keadaannya saat ini (kontra) kepada orang lain:
    class TellCountCommand : IMessage { }
    

  4. Suatu peristiwa yang memberi tahu aktor lain bahwa aktor telah memberi tahu semua orang tentang keadaannya saat ini (counter). Dihasilkan ketika perintah TellCountCommand diproses:
    class SaidCountEvent : IMessage
    {
        public int Count { get; }
        public int ActorId { get; }
    
        public SaidCountEvent(int count, int actorId)
        {
            Count = count;
            ActorId = actorId;
        }
    }
    

    Hitungan adalah berapa banyak yang sekarang diputar di meja aktor dengan pengidentifikasi yang sama dengan ActorId
  5. Perintah ini memberi tahu aktor untuk mencetak pesan ini ke konsol:
    class WriteMessageCommand : IMessage
    {
        public string Message { get; }
    
        public WriteMessageCommand(string message)
        {
            Message = message;
        }
    }
    

  6. Mulai instance aktor yang mengontrol status penghitung saat ini:
            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. Membuat aktor yang hanya menulis pesan ke konsol:
            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();
            }
    



Nah, jadi itu akan benar-benar indah dan terlihat seperti grale (aktor) dari Orleans, mari kita membuat bungkus di meja kami:
    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());
        }
    }

Di sini model reaktif digunakan. Aktor kami tidak mengembalikan nilai, tetapi hanya menghasilkan pesan baru dengan nilai yang diinginkan. Di sini kita memiliki satu aktor hanya di utasnya sendiri, utas aman dari orang lain, menambah penghitungnya. Aktor lain hanya menulis ke konsol. Namun, mari kita bayangkan bahwa bagi kita setiap aktor, misalnya, menghitung koordinat saat ini, kesehatan, dan manna dari satu dari sepuluh pemain yang saat ini bermain di server kami. Masing-masing dengan keadaannya sendiri dalam alirannya sendiri. Kami mengirim pesan ke orang lain atau menerima pesan dari orang lain tentang kerusakan yang diterima, keterampilan yang digunakan, atau perubahan koordinat kami.

All Articles