반응형

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        //async/await : 비동기인데 멀티스레드가 아닌 경우가 있다  : coroutine 와 유사하다

        //task 는 일감을 말함
        static Task test()
        {
            Console.WriteLine("start test");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);
            return t;
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            Task t = test();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

async/await : 비동기인데 멀티스레드가 아닌 경우가 있다  : coroutine 와 유사하다

 

Task 를 생성하고 delay 에 시간을 넣으면 생성과 동시에 시간이 흘러간다는 것을 알 수 있다

 

start test 문자가 뜨고 시간 지연 없이 바로 while start 가 호출되는데

        static Task test()
        {
            Console.WriteLine("start test");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);
            return t;
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            Task t = test();

            //처리 지점
              
            t.Wait();
            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }

wait 이 들어가면 start test 가 찍힌 후 3초 이후에 while start 가 호출 되는것을 알 수 있다

 

그래서 //처리 지점    에서 task 가 완료 되는 동안 다른 동작을 할 수 있게 된다

 

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async void testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            Task t = Task.Delay(3000);

            await t;
            Console.WriteLine("end testAsync");
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

 

이 코드는 async/await 인데 결과는 다음과 같다

async 함수가 호출되고 task 가 완료 될때까지 await 으로 기다려지긴 하지만 제어권이 main 으로 넘어가서 처리할 것을 먼저 처리 할수 있는 형태가 된다 typescript 에 이와 유사한 문법들이 있다

 

또는 다음 처럼 간략한 버전으로 사용 할 수도 있다

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async void testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");
        }

        static void Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

 

 

main 에서 testAsync 함수가 완료 될때까지 대기 시킬 수도 있다 다음 처럼 대기 할수 있고

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");
        }

        static async Task Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            await testAsync();

            Console.WriteLine("while start");
            while(true)
            {
                
            }
        }
    }
}

testAsync 함수가 모두 완료 될때까지 awiat testAsync(); 구문에서 대기하다가 완료 된 이후 main 의 나머지 구문을 이어 나간다

결과는 다음과 같다

 

 

값을 리턴 받고 싶다면 Task<T> 를 리턴하면 된다

 

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task<int> testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");

            return 10;
        }

        static async Task Main(string[] args)
        {
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            int retValue = await testAsync();

            Console.WriteLine("while start " + retValue);
            while(true)
            {
                
            }
        }
    }
}

 

 

 

다음처럼 main 에서 다른일을 하기위해 task 를 별도의 변수로 두어 위 async 함수를 분리하여 동시에 처리할 일을 처리 할 수도 있다

using System;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static async Task<int> testAsync()
        {
            Console.WriteLine("start testAsync");

            //3초 후에 task 를 반환한다
            await Task.Delay(3000);
            Console.WriteLine("end testAsync");

            return 10;
        }

        static async Task Main(string[] args)
        {
            Task<int> t = testAsync();
            Console.WriteLine("다른 일");
            //위에서 task 를 만듬과 동시에 시간은 흐르게 된다
            int retValue = await t;

            Console.WriteLine("while start " + retValue);
            while(true)
            {
                
            }
        }
    }
}

 

 

 

예시 다음 처럼 조식을 만들때

다음 처럼 일반적인 방식인 순차적으로 생각 할 수도 있지만

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            Egg eggs = FryEggs(2);
            Console.WriteLine("eggs are ready");

            Bacon bacon = FryBacon(3);
            Console.WriteLine("bacon is ready");

            Toast toast = ToastBread(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("toast is ready");

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

 

 

async 함수를 적절히 await 하여 처리 하여 대기 시간을 줄일 수 있다

그림은 위에서 아래로 내려가는 방향의 시간의 흐름이고 옆은 비동기로 실행 되는 개수라 보면 된다

 

 

 

Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Console.WriteLine("Breakfast is ready!");

 

 

최종 방식은 List 로 Task 를 담아 대기 하는 방식으로 최종 15 분 안에 끝나는 예시이다

 

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static async Task Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            var eggsTask = FryEggsAsync(2);
            var baconTask = FryBaconAsync(3);
            var toastTask = MakeToastWithButterAndJamAsync(2);

            var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
            while (breakfastTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(breakfastTasks);
                if (finishedTask == eggsTask)
                {
                    Console.WriteLine("eggs are ready");
                }
                else if (finishedTask == baconTask)
                {
                    Console.WriteLine("bacon is ready");
                }
                else if (finishedTask == toastTask)
                {
                    Console.WriteLine("toast is ready");
                }
                await finishedTask;
                breakfastTasks.Remove(finishedTask);
            }

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);

            return toast;
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            await Task.Delay(3000);
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            await Task.Delay(3000);
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

 

ref : https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/

 

반응형

'프로그래밍(Programming) > C#' 카테고리의 다른 글

linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
Common Memory Leaks In C#  (0) 2022.12.04
반응형

셋 set (중복을 허용하지 않는 집합)

합집합, 교집합 , 차집합

 

채널을 구독할때 중복 구독되지 않도록 할때 유용

 

sadd

sinter

srem

 

추가가 되면 1 이 되고 이미 추가가 되어 있다면 0이 된다

이때 는 faker:gudok 이 되고 abc가  member 가 된다

 

 

sinter 로 faker:gudok 를 통해 읽어오면 member 인  abc 를 얻어올 수 있다

 

 

abc1 과 abc2 를 추가한뒤 sinter 로 값을 보면 정렬되어 있진 않고 중복 없는 값이 들어간걸 알 수 있다

 

 

삭제는 srem 으로 삭제 할수 있다

 

 

 

 

 

 

정렬된 Set

 

zadd, zrange 로 다음 처럼 정렬된 랭킹 정보를 구할 수도 있다

 

zadd 할때 중간에 숫자를 꼭 넣어줘야 한다, 이 숫자로 정렬된다

 

127.0.0.1:6379> zadd test:ranking 300 book
(integer) 1
127.0.0.1:6379> zadd test:ranking 200 store
(integer) 1
127.0.0.1:6379> zadd test:ranking 700 board
(integer) 1
127.0.0.1:6379> zrange test:ranking 0 10
1) "store"
2) "book"
3) "board"
127.0.0.1:6379>

 

숫자값에의해 정렬된것을 알 수 있다

 

 

리트스의 경우

lpush, rpush, lrange, lpop, rpop 등의 명령어가 있고

lrange key 0 -1 을 쓰면 전체 리스트 내용을 볼 수 있다

 

 

 

 

해시 (해시 기반)

hset :  다음 순으로 데이터를 넣게 된다

key 는 전체 카테고리 key 이고 field 와 value 가  <key, value> 가 된다

 

hget

hlen : 개수가 몇개인지

hdel : 삭제

hgetall : 모든데이터 보기

 

 

 

다음은 한번에 다른 필드로 여러 항목을 추갛 ㅏ는 방식을 보여준다

 

 

REF : 

http://redisgate.kr/redis/command/zadd.php

https://redis.io/commands/sinter/

반응형
반응형

https://github.com/tporadowski/redis/releases

 

Releases · tporadowski/redis

Native port of Redis for Windows. Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Se...

github.com

.zip 압축을 풀고 sever 를 실행시킨다음 cli.exe 로 클라이언트에서 테스트 해볼 수 있다

 

 

서버

 

클라 기본 상태

 

key1 으로 값 hello world를 저장

 

 

get 으로 가져올 수 있다

 

즉 이렇게 메모리상에 들고 있게 되는 것이다

 

 

서버를 끄고 get 을 요청하면 아무 일도 일어나지 않게 된다, 데이터도 역시 날라가게 된다

 

 

다시 서버를 키고 get 을 해보면 nil 이 나온다

 

set user:name "abc"

 

get user:name

"abc"

 

이렇게 나오고

append user:name " is good"

get user:name

"abc  is good"

 

이 된다

 

 

incr : 11은 문자인데 문자를 숫자로 인식하여 1을 증가한다음 12로 찍는 명령어

 

 

mset, mget  : 여러개를 설정하거나 가져올 수 있는 것

 

 

기존 키 값을 set 으로 다시 쓰게 되면 덮어써지게 된다

 

 

ttl : 유효 시간이 얼만나 남았는지 확인, -1 , 이면 무한대를 말한다

expire : 유효시간 설정

 

 

 

유효시간을 설정하고 ttl 을 호출하면 남은 시간을 볼 수 있다

 

서버에서 세션에 대한 정보를 DB 에서도 처리 할 수 있다

먼저 ID 와 pw 가 맞는지는 웹서버에서 검증한다 그리고 이 유저가 맞는지는 DB 를 통해서 인증을 거치는데

인증을 통과했다면 인증 받은 사람이라는 토큰을 클라에게 전달해준다

 

username:세션ID 를 하나의 컬럼에 설정한다음 유효시간을 별도로 설정한다

세션 id 가 존재하는지에 대해선 username 의 세션 ID를 확인하여 존재하는 유저인지 알 수 있고

관계형 DB 에선 이렇게 username 의 세션 ID를 클라에게 던져주는 형태가 된다

즉 클라가 서버에 다시 뭔가 할때 세션ID를 던진다

 

이렇게 되면 매번 서버와 DB 를 왔다갔다 하며 유효시간을 별도로 설정해줘야 한다는 것등의 별도의작업 및

불필요한 비용이 발생 할 수 있다 어차피 세션 ID가 서버에선 유효한 값 이므로
(클라에서 변조된것인지 서버에서만 판단해 주면 됨으로) 이때 redis 가 좋은대안이 될 수 있다

 

 

 

 

반응형
반응형

관계형 DB 는 B-Tree 기반으로 이뤄지고

데이터가 엄청 많아지면(1억 단위를 넘어간다면) 느려진다는 단점이 있다

MMORPG 는 대부분 1억 단위인 빅데이터 단위로 넘어가지 않는다 

그리고 한 서버군마다 동접이 5000 명에서 10000명이다

 

그런데 만약 데이터간의 관계가 떨어져서 Key-Value 로 관리 해도 무리가 없다면 NO-SQL 형태를 고려해 볼 수 있다

Hash 를 떠올리면 된다

그리고 Value 에는 문자열, 리스트, 셋, 정렬된 셋, 해시를 넣을 수 있다

 

그리고 NO-SQL 은 In memory 방식으로 전원을 끄면 데이터를 날라가는 방식이다

 

 

중요한 데이터는 관계형 DB 로 저장하고 휘발성인 랭킹 같은것들은 redis 로 사용 하는 것을 검토해 볼 수 있다

Redis 는 기본적으로 Linux 에서 돌아간다 (개발 단계에서 window 에서 돌릴 수는 있다)

 

대규모 게임이 아닌 소규모에서 redis 윈도우를 사용하거나

윈도우환경에선 redis 대용으로 나오는 memuria 를 고려해볼만 하다

 

https://www.memurai.com/

 

Redis for Windows alternative, In-Memory Datastore | Memurai

" Migrating from a legacy hardware and software system to the Microsoft stack was an interesting journey. Our in-house developers are well versed and trained on Windows, .NET, Azure and other tools available for the Microsoft ecosystem. We explored several

www.memurai.com

 

반응형
반응형

Read 에서 전반적인 흐름은 유저가 요청을 하면 서버에서 데이터 베이스에 자료를 요청하고 데이터베이스는 큰 램에서 데이터가 있다면 리턴 그렇지 않다면 하드에서 읽어 리턴하는 형식이 된다

 

노란색이 유저이고 데이터 read 요청을 한다면 노란색 박스인 램 즉 하드에서 미리 올려놓은 램에서 보고 데이터가 있다면 리턴하고 그렇지 않다면 하드에서 읽어들여온다 

만약 램이 다 차면 LRU 방식 등으로 날린다

LRU 알고리즘 : 가장 오랫동안 참조되지 않은 페이지를 교체하는 기법

 

램엔 데이터뿐만 아니라 인덱스 또한 올려 놓게되어 빠른 처리를 하는데 돕게 된다

 

 

 

 

쓸때는 쓰는 스레드를 빽단에서 돌려 별도로 쓰게 하여 하드로 접근하여 데이터를 다 쓸때까지의 대기 시간을 줄이는 방식이다

 

유저가 서버에 요청하면

SQL 파싱 처리후 DB 에 쓰기 명령 을 보낸다

데이터베이스에서 빽그라운드에서 쓰기를 처리한다

 

 

락의 종류

 

 

shared 는 read_write rock 을 말한다 : 읽을때는 동시에 동기화 부하 없이 읽다가 

쓸때는 한번에 하나만 처리 되는 방식이다, 다른 스레드는 진입 불가

 

Exclusive 는 mutex 같은 것이라 생각하면 된다

 

Update 락은 shared, exlusive 준간 정도로 처음엔 shared 로 동작하다가 상황에 따라 Exclusive 로 동작하는 락이다

 

 

row 에 가까워 질 수록 락 범위가 작아지고 윗단에서 잡을 수록 전반적으로 느려지고 자원소모가 덜해지는 경향을 보인다

ex ) row 를 몇백개 잡는 일이 발생한다면 Page 단에서 lock 을 잡는 것이 더 빠를 수 도 있다 => 하지만 어차피 데이터베이스에서 알아서 처리한다

 

 

 

트랜잭션 특징 ACID

 

1,4번이 중요함

 

원자성 : 만약 두개의 정보를 동시에 변경 해야 할때 한개만 변경되고 완료 처리가 되면 안된다는 것

 

지속성 : 물리적인 장애로 문제가 발생해도 저장해야 하는데, 만약 데이터 저장이 실패하면 롤백해야함

=> DB 에서 어떤 작업을 할때 항상 로그를 남긴다 => 이 로그를 통해 정전이 일어나서 중간 과정이 날라갔다 하더라도 로그로 기록은 남아 있기 때문에 해당 부분의 작업을 되살리거나 되돌릴 수 있다

 

 

 

 

반응형

'서버(Server) > DB' 카테고리의 다른 글

redis (2) 설치 와 기본 사용법과 컨셉  (0) 2023.04.05
NOSQL - Redis (1)  (0) 2023.04.04
[MSSQL] 예상 실행계획  (0) 2023.04.02
Clustered(바로 찾기), Non-Clustered (경유해서 찾기)  (0) 2023.03.03
DB : 복합 인덱스  (0) 2023.03.02
반응형

% 가 클 수록 시간이 더 걸린다는 것임으로 큰 쪽의 % 를 낮추는 작업을 해주면 된다

 

CTRL + M 으로 실행계획을 볼 수 있다

 

 

 

ref : https://m.blog.naver.com/seek316/222306507699

반응형
반응형

[지연시간]

유선 네트워크에서는 지연시간이 10ms, 많아야 20~30ms 인데

와이파이에서는 100ms 에서 불안정하면 200~300ms 까지도 불안정하게 발생하게 된다

보통 게임에서 30~60 프레임 기준을 많이 사용하는데

60프레임 기준에선 한프레임당 16ms 정도 의 시간이 걸린다

이 시간을 넘어가면 사람들이 인지는 하기 시작하고 

30프레임에선 한 프레임당 33 ms 인데 33ms 가 넘어가면 거의 모든 사람들이 부자연 스럽다고 인지하게 된다 



[예측과 보정]

가지고 있는 정보들을 통해서 미리 예측을 하고 정보를 받았을때 예측과 결과가 다르다면 보정을 해주는 방식 

 

ex ) 타격이 먼저 되고 때리는 모션이 나중에 나가는 경우 

논타겟의 경우 시점 분리가 된다 : 스킬 시전 시점과 스킬 피격 대상 선정 사이에 차이가 발생

만약 스킬 영역에 스킬을 시작하고 스킬이 발생 하는데 까지 시간이 걸리는데 영역안에 있던 대상이 밖으로 도망가면 피격이 되면 안된다, 이때는 피격 당하는 대상의 시점만 미뤄두고 나중에 데미지가 가해지게 하면 되지만 시점분리에 필요한 요구사항이 점점 늘어난다면?

 

ex) 스킬 대상이 넉백 되면서 데미지는 그 도중에 되면 좋겠다

: 1. 시작은 따로 2. 그다음에 넉백.  3 그다음에 데미지  이렇게 3개로 분리가 된다

 

영역은 하나인데 대상을 3번에 걸처서 타격해야 하는 상황이 발생한다

 

더 나아가 쿨 다운은 선딜 후 타격 전에 돌리고 싶다 

내가 스킬 시전하다가 넉백을 당하면 취소가 되야하고

기를 모으는 시간에 따라 효과가 다르게 하고 싶다

돌진하면서 주변에 있는 적들을 차례로 밀치고 싶다

 

등등등.. 요구 사항이 점점 더 많아짐..

 

이때 생각해 볼 수 있는거 스킬 스테이지 방식이다

 

스킬을 여러 단계로 나누어서 구성 하는 것을 말한다

 

그런데 문제는 결정된 시간이 전달이 될때 받는 쪽에서 지연이 되면 내가 원하는 타이밍에 전달되지 않게 된다 




아래처럼 동작하겠지 라고 생각하고 만들었는데



네트워크를 타는 순간 스킬 구성이 달라지게 된다 

 

이 처럼 시간 간격에 딜레이가 생기면서 타격이 늦게 일어나거나 여러 가지 시점이 흔들리는 일들이 벌어지게 된다 

 

그래서 나온게 예측 가능하게 만들자

여러 스테이지 결과를 미리 결정 및 공유하고 

이에 맞춰 각자 서버와 클라가 스킬 스테이지를 진행한다

그래서 이때 미리 계산 할 수 있는 그룹들을 미리 묶어 두어서 처리하게 된다

 

그것이 스킬 스테이지 플로우 도입

 

CapturingTarget 영역 안의 대상 선정 

이나 중요하게 반드시 시점이 나뉘어야 하는 것들만 플로우를 나누는 기준이 되고 나머지 스테이지들은 묶어서 미리 계산을 하도록 처리




이걸 



이렇게 묶음으로 써

원래 4번 전송 되던걸 두번만 전송하게 되고 묶음 안에서는 어색함 없이진행 가능

 

문제점 : 플로우가 길다보면 발생할 수 있는문제가 중간에 취소 하면 취소가 늦게 가는 경우 

피격을 하지 않았는데 피격 연출이 나온다던가 하는 문제

 

해결 방안 => 정상 타격이 완료 되면 데미지 숫자를 붉은색으로, 그렇지 않으면 파란색으로 표시하여 대미지가 안들어간것으로 보이게끔 처리

 

또는 스킬 연출과 별개의 추가 연출 구간을 만들어서 

정상타격 완료 시 : 대미지 플로터로 확정 연출

정상타격 취소로 판정시(늦게 패킷이 오면) : 대미지 플로터를 모래 연출처럼 흩어 버리기

방식으로 처리




 

논 타겟일때 움직이면 맞고 가만히 있으면 맞을때도 있고 안맞을때도 있는데

 

=> 타격자 시점에서는 상대 캐릭터가 스킬 범위에 들어갔는데 피격자에서는 안맞는 문제가 보임 즉 내 캐릭터가 공격을 하는데 상대 캐릭터가 스킬 범위에 들어왔음에도 불구하고
공격이 가해지지 않음


=> 피격자 시점에서는 타격자가 공격 당하는 상대로 스킬을 논타겟으로 썼지만 상대가 스킬 범위에 들어가 있지 않아서 공격을 받지 않은 상황으로 보이는 중

 

 

이건 플레이어 위치가 타격자와 피격자 시점에서 위치가 살짝 다를때 발생 할 수 있다

서버상에서도 피격자의 위치가 실제 달라서 발생 

타격자 시점에서 상대방 위치를 보면 조금 더 왼쪽으로 가 있는 상태














이때 이동 시스템이 어떻게 되어 있는지 알 필요가 있다

 

폴리드 타일기반으로 이동하는 시스템 

셀도 없고  방향도 특정되지 않고 자율롭게 돌아다닐 수 있는 구조

위치를 짧은 시간내에 동기화 할 수 있는 모델은 없었다는 것 MMORPG 라

대규모 전투에서 통신량이 큼으로 유저가 많을 수록 제곱에 비례해서 늘어나기 때문에..



[이동에 관한 히스토리]

  1. 클라가 서버에게 이동 할래 라는 패킷을 날리면 서버가 판단해서 이동을 시키는 구조
  2. 결국 클라이언트에서 먼저 이동하도록 변경




즉시 예상하지 못하는 상황에서 멈춤을 할때 를 생각해 보면
다른 유저, 서버에서의 해당 유저, 내클라이언트에서 다른 유저를 멈출때의 위치가 모두 달라지게 된다

즉 피어들간의 위치 정보의 오차가 커진다

 

멈추는 클라이언트에서는 빨간 위치에서 멈추라고 했지만 서버와 다른 클라에서 모두 다 조금씩 늦게 받게 되어 느려지는 현상이 발생 할 수 있다



순간적으로 변경한 명령이 다음 이동 이다 라고 하면 

이동의 시작 구간에서 보정을 진행하면 된다 이때는 이동을 하는 와중에 스킬을 잘 맞고 위치 오차도 적은 편이다

하지만 바로 멈추라고 하면 다음 이동이 없다, 즉 멈추라고 하면 다음 이동이 없어 보정할 곳이 없다 

=> 만약 원래 멈춰야 하는 곳보다 더 갔을때 캐릭터를 뒤로 당기면 이동이 어색해 지기 때문에 그 자리에 있을 수 밖에 없도록 프라시아에서는 작업을 했었고 다음 이동 명령때 다시 보정을 한다를 컨셉이였었다  => 그러다보니 생각하는 위치들이 다 달랐던 것 

 

해결  : 구간이 없어도 강제로 맞춰줘야 되는 부분이기 때문에 기본적인 해결책 부터 적용 시작

방안 :

  1. 멈추면 그 자리로 그냥 옮기자

 

핑이 안정적일때는 40ms 에서 120ms 까지는 그래도 안정적으로 추가 이동명령으로 위치 보정을 해도 어색하지 않고 추가로 멈춰야 할 위치까지 이동해서 보정이 가능하다

그런데 이미 이동을 멈춰야 하는 위치보다 더 가 있을때 이걸 당기는건 게임을 망치기 때문에 

 그 자리에 멈출 수 밖에 없다 즉 50% 확률이 보정이 되거나 안되거나 하는 상황

 

정확(무보정)의 수치를 200ms 뒤 까지에서 실행되게 해야하는 것이 목표인데

즉 대부분이 보정이 될 수 있게 처리를 해야 하는데

 

이렇게 처리 하기 위해선 내쪽에서

다른 개체의 이동 요청이 왔을때 120에 받았는데 마치 200에 받은 것 처럼 처리해 주면 된다

 

즉 다른 개체의 이동 정보는 한 80ms 늦게 실행 시킨다 

이렇게 하면 보정을 더 과하게 해야하긴 하지만 아예 보정을 할 수 없는 케이스는 거의 사라지게 된다

 

위의 문제는 이동에 관한 갑자기 멈춤에 관한 문제였음으로 상대 캐릭터 이동에 대한걸 80ms 정도 지연시켜 받아서 이동에 대한 보정 처리를 해주면 결과적으로 200ms 내에 이동 보정이 됨으로 타격은 정확한 처리가 된다 => 문제는 해결됐다
수치상 80ms 이긴 한데 상황에 따라 다를 수도 있으니 동적으로 다르게 주는 것도 생각해 볼만한 문제 



그런데 이동하는 중에는 오차가 좀 더 커진 셈이긴 하지만 캐릭터가 스킬을 안맞는 크리티컬한걸 피했다고 볼 수 있다



정리

 

  • 예측을 활용 하는 동기화 방식은 어느정도 활용 중
  • 하지만 모든 정보를 알리는 방식으로 동기화를 할 경우 과다한 네트워크 통신, 느린 반응성등의 문제가 발생 할 수 있다
  • 시간의 흐름에 따른 상태를 계산해 낼 수 있는 일부 상태만 알림을 통해 동기화 처리 한다 => 정해진 규칙에 따라 현재 상태를 도출한다
  • 네트워크를 통하면 외부 요인으로 인해 상태가 변경 되었을 때 이 정보를
    내가 늦게 알게 되는 경우가 자주 발생한다, 
  • 내가 계산해서 예측을 해서 가지고 있는 현재 상태랑 믿을 수 있는 소스 보틍은 서버와 상태가 서로 다를 경우 현재 내가 가지고 있는 상태를 실제 상태로 자연스럽게 맞추는 작업을 보정이라고 한다 

 

만약 더이상 자주 알려주기 힘들다면 고려해볼만 한 것은

이른 시점에 결정적인 상태를 만든다, 먼저 계산해서 알려준다 는것 

 

도출해낸 결과가 정확한게 가끔 많이 틀릴 수 있다면
엄청 많이 틀리는 것을 좀 완화하고 대신에 평소에는 조금 정확성이 떨어지더라도 

워스트 케이스를 없애는 게 더 좋을 수 있다

다만 정확성을 너무 포기하면 보정이 힘들어진다 

구간이 없다면 80ms 정도 늦게 받아서 처리 하는 것처럼 구간을 만들어서 구간을 활용하는 전략들을 고민하면 좋을 것 

 

예측에는 Extrapolation 외삽법이 주로 쓰이고 보간에는 interpolation 이 주로 쓰인다

 

 

 

--------------------

별도 추가 부분

--------------------

 

외삽법 : 외삽법이 뭔지 오래 되서 잘 기억이 안날 수 있음으로 복습차원에서..

검은색이 원래 데이터이고 파란색이 예측하는데이터인데 점3개에 대해서 보간을 하면 원래 검은색과 거의 유사하게 파란색이 나오는데 그 미래에 대한 4번째 점에 대해선 오차가 심해 질 수 있다는 외삽법의 단점이 있다 

고차다항식의 보간법의 위험성 : 항이 많아지고 차수가 높아 질 수록 식이 정교해 질것이라는 기대가 있지만 오히려 않좋은 결과가 나올 수도 있다 

 

점선이 정확한 값인데 점 4개를 갖고 4차 다항식을 만들 수 있고 그림을 그리면 검은 선이 나오는데 형태는 조금 유사해도  오차가 나온다 

이때 더 많은 점을 도입해서 10차 다항식으로 접합을 해보면 

runge 함수를 다항식으로 묘사를 해봤더니 양 끝단에 오차가 상당히 크게 나온걸 알 수 있다

즉 다항식 보간법을 쓸때 고치일 수록 정확하다고 볼 수 없다는 것이 Runge 라는 사람이 만든 Runge 함수이다, 오히려 낮은 차수 일때 더 유사할때가 있다는 것 

 

 

 

 

 

이글의 원문 :

 

● 발표분야: 프로그래밍
● 발표자: 넥슨코리아 정성훈 / Nexon Korea Sunghoon Jung
● 권장 대상: 프로그래머, 시스템 디자이너
● 키워드: #네트워크 #동기화 #연출 #프라시아전기

반응형

+ Recent posts