반응형

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //join
            {

                List<int> levels = new List<int>() { 1, 5, 9};

                var playerLevels = 
                    from p in _players
                    join l in levels            //player와 levels를 조인하는데
                    on p.Level equals l     //레벨 i 와 player.level 이 같은것만 조인한다
                    select p;

                foreach(var p in playerLevels)
                {
                    Console.WriteLine(p.Level);
                }
                int test = 0;
            }

            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

player와 levels를 조인하는데 레벨 i 와 player.level 이 같은것만 조인하게 한다

 

                 List<int> levels = new List<int>() { 1, 5, 9};

                var playerLevels = 
                    from p in _players
                    join l in levels            
                    on p.Level equals l    

                    select p;

 

 

 

 

 

표준 Linq 방식

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }


            //linq 표준연산자
            {
                var players =
               from p in _players
               where p.ClassType == ClassType.Knight && p.Level >= 50
               orderby p.Level
               select p;

                //위 결과와 아래 결과는 같다

                //from 은 생략 가능
                var sameResult = _players
                    .Where(p => p.ClassType == ClassType.Knight && p.Level >= 50)
                    .OrderBy(p => p.Level)
                    .Select(p => p);


                int iii = 0;
            }

두개의 결과가 같은 것을 볼 수 있다

 

            //linq 표준연산자
            {
                var players =
               from p in _players
               where p.ClassType == ClassType.Knight && p.Level >= 50
               orderby p.Level
               select p;

                //위 결과와 아래 결과는 같다

                //from 은 생략 가능
                var sameResult = _players
                    .Where(p => p.ClassType == ClassType.Knight && p.Level >= 50)
                    .OrderBy(p => p.Level)
                    .Select(p => p);

 

결과 : 

세부적인 기능을 사용하고 싶다면 함수형 linq 를 사용하면 된다

 

반응형

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

default 연산자  (0) 2023.04.25
linq (2)  (0) 2023.04.09
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
반응형
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }
            


            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

palyer 에 

 public List<int> Items { get; set; } = new List<int>();

를 추가 한다음

 

from from 구문으로 item 의 모든 목록을 출력하는 구문이다

 

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }

 

 

 

 

Group 처리

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
        public List<int> Items { get; set; } = new List<int>();
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                for(int j=0;j<5;++j)
                {
                    player.Items.Add(rand.Next(1, 101));
                }
                

                _players.Add(player);
            }

            //중첩 from , ex : 모든 아이템 목록을 추출할때
            {
                var items = from p in _players
                            from i in p.Items
                            where i > 95
                            select new { p, i };

                var li = items.ToList();
                foreach(var elem in li)
                {
                    Console.WriteLine(elem.i +" : " + elem.p);
                }
            }

            //group
            {
                var playerByLevel = from p in _players
                                    group p by p.Level into g           //g 로 그룹화된 데이터를 받는다
                                    orderby g.Key
                                    select new { g.Key, Players = g };
                int jj = 0;
            }


            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

코드 중

            //group
            {
                var playerByLevel = from p in _players
                                    group p by p.Level into g           //g 로 그룹화된 데이터를 받는다
                                    orderby g.Key
                                    select new { g.Key, Players = g };
               
            }

이 부분이 플레이어들을 같은 level 로 그룹화 한다음 각 그룹끼리는 key 값에 의해 정렬 되도록 처리한 것이다

into 는 그룹화 한것을 g 라는 집합으로 넣겠다는 것

 

결과 : 

 

반응형

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

default 연산자  (0) 2023.04.25
linq (3)  (0) 2023.04.10
Linq (1)  (0) 2023.04.08
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
반응형

 

 

T1 과 젠지(Gen)이 영혼의 한타를 펼친다

이번에도 회전 초밥 슈퍼플레이가 배달될 것인가

 

 

 

 

 

https://www.youtube.com/watch?v=mV77sRWO1Us

반응형
반응형
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp2
{

    public enum ClassType
    {
        Knight,
        Archer,
        Mage
    }

    public class Player
    {
        public ClassType ClassType { get; set; }
        public int Level { get; set; }
        public int HP { get; set; }
        public int Attack { get; set; }
    }

    class Linq
    {
        static List<Player> _players = new List<Player>();

        static void Main(string[] args)
        {

            Random rand = new Random();

            for(int i=0;i<100;++i)
            {
                ClassType type = ClassType.Knight;
                switch(rand.Next(0, 3))
                {
                    case 0:
                        type = ClassType.Knight;
                        break;
                    case 1:
                        type = ClassType.Archer;
                        break;
                    case 2:
                        type = ClassType.Mage;
                        break;
                }

                Player player = new Player()
                {
                        ClassType = type,
                        Level = rand.Next(1, 100),
                        HP = rand.Next(100, 1000),
                        Attack = rand.Next(5, 50)
                };

                _players.Add(player);
            }

            GetHightLevelKnights();


        }

        //레벨이 50 이상인 knight 만 추려서 레벨을 낮음->놎음 순으로 정렬
        private static void GetHightLevelKnights()
        {
            //linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
            //from 은 foreach 로 생각해도 괜찮다
            //실행 순서는 from where orderby select 순으로실행된다 생각하면 된다
            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

            foreach (Player p in players)
            {
                Console.WriteLine($"{ p.Level} {p.HP} {p.ClassType}");
            }
        }
    }
}

 

 

linq 문법으로 db 쿼리문 처럼 조회 할 수 있다
from 은 foreach 로 생각해도 괜찮다
실행 순서는 from where orderby select 순으로실행된다 생각하면 된다

=> 이것이 원래 db 의 쿼리 구문을 이해하는 순서인데 c# 의 linq 에서는 select를 아예 밑으로 내려놨음


            var players =
                from p in _players
                where p.ClassType == ClassType.Knight && p.Level >= 50
                orderby p.Level
                select p;

 

 

 

유니티에선 linq 가 ios 버전에서 문제가 발생 할 수 있어서(불안전해서) 안쓰는게 좋다

(현재 버전 확인 필요)

 

서버에선 linq 활용을 하기에 DB 와의 명령어등의 호환성 좋아서 사용하는것은 괜찮다

 

 

db 관련 참고글 https://3dmpengines.tistory.com/category/%EC%84%9C%EB%B2%84%28Server%29/DB

 

'서버(Server)/DB' 카테고리의 글 목록

[3D] [M]athematic [P]hysics Engines

3dmpengines.tistory.com

 

반응형

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

linq (3)  (0) 2023.04.10
linq (2)  (0) 2023.04.09
async/await & 커피와 베이컨  (0) 2023.04.07
C# - ArraySegment  (0) 2023.01.04
ThreadLocal<T> 와 AsyncLocal<T> 의 차이점  (0) 2022.12.29
반응형

 

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
● 권장 대상: 프로그래머, 시스템 디자이너
● 키워드: #네트워크 #동기화 #연출 #프라시아전기

반응형
반응형

 

서버에는 탈것 에대한 상태만 있고 오브젝트는 없음

크랄에서 탈것의 비주얼을 보여줌 

 



  1. 타기요청패킷 : 일반 상태에서 서버에 부르기를 요청을 시작하면서 타기가 시작됨
    1. 이때 서버는 타기에 필요한 검사를 함
      캐릭터의 이동 방향을 보고 탈것이 캐릭터를 태우러 갈 수 있는지 확인 필요
      이때 네비게이션 메시 상에서도 유효한 경로인지 같이 검사를 한다
  2. 부르기 패킷(만날위치포함) : 준비가 끝나면 문제가 없다 판단 되면, 
    1. 모든 클라에게 탈것에 대한 만날 위치포함하여 부르기 알람 메세지를 보낸다 
      그리고 탈것이 어디로 갈지에 대한 정보도 포함한다
    2. 부르기에 필요한 시간을 기다렸다가 랑데부 알람 메세지를 클라에게 보내기 위해 서버에서 예약한다
  3. 랑데부 패킷 : (랑데부는 만남, 만나는 장소라는 뜻) 
    1. 클라에서 랑데부를 받으면 클라에서 타기지점으로 뛰기 시작한다
    2. 탈것을 스폰하고 캐릭터를 향해 달리게 한다
      캐릭터는 이 지점을 향해 달려 가는 연출을 한다
      이때 캐릭터는 탈것을 부르면서도 계속 달리는 중임으로 부르기 상태에서부터 다른 이동의 간섭을 막고 탈곳과 만나기 위한 곳으로 달려 나간다
      이때 캐릭터와 탈것은 계속 달리는 중이다 
  4. 오르기요청 : 클라가 캐릭터가 탈 준비가 완료 되면 서버에 알려주는 방식
    클라에서 서버로 알리는데 => 캐릭터와 탈것은 계속 달리는 중이기 때문에 서버와 메시지를 주고 받는 동안에도 위치가 계속 바뀌고 있는 중임으로 이때 오차가 발생 할 수 있는데 이를 줄이기 위해  
    1. 캐릭터와 탈것의 위치를 서버로 같이 전달한다
    2. 서버에서는 탈것의 위치가 탈것에 올라타는 캐릭터의 새 위치가 되기 때문에 이 둘의 범위가 약속한 범위 안쪽으로 들어오면 
      모든 클라에게 오르는중패킷을 보낸다
    3. 나 자신은 이미 이전에 오르는 연출을 시작 하기 때문에 오르기요청에 대한 응답 패킷은 무시하지만
      다른 클라는 오르는중 패킷을 받으면 오르는 연출을 시작한다
  5. 오르기 완료 패킷 : 오르는 중이 완료 되는 시간이 정해져 있고 이 시간이 완료 되면 상태와 오르기완료패킷을 클라들에게 보내어 오르기를 완료한다, 

 

위 이미지인데 타는 경로를 아래 처럼 생각 해볼 수 있다

 

캐릭터 오르는 연출

  • 랑데부 에서
    • 캐릭터와 탈것이 각각 어느 위치에 있을때 오르기가 시작되는지를 결정해야한다
      캐릭터가 오르기 시작 애니가 나가고 -> 탈것 기준에서 오르기 시작한 지점과 오르기 완료된 지점까지 완료 되면 이때 약간 더 앞으로 전진하면 원래 캐릭터가 이동하던 직직방향과 만나게 된다, 그리고 오르기 완료시까지 플레이어 입력을 일시적으로 제한한다
      일시적으로 제한되는 사이에 연출이 일어나기 때문에 이때의 시간은 기획자가 알아서 제어하도록 테이블로 빼놓음, 말이나 탈것에 따라 연출이 달라질 수 있기 때문 
      그리고 타는 동안 속력은 기존 그대로 고정한다
  • 오르기 완료 
    • 캐릭터 액터를 탈것 액터에 부착하는 방식
      • 캐릭터와 탈것의 위치와 방향은 다르지만 일단 붙여놓고 애니메이션을 재생 하면서 일정시간동안 위치와 방향의 차이가 0이 되게끔 한다
      • 이 시간은 오르기 애니메이션 시간 중에서 캐릭터가 탈것에 닿을 때까지의 시간 , 이때 위치와 방향은 형태와 상관 없이 선형적으로 처리
      • 이때 카메라는 캐릭터 위치를 따라가게 한다음 타기가 완료되면 원래 카메라를 제어하던 곳으로 반환 시켜준다
  • 모바일에서 인터넷이 일시적으로 안되는 상황을 대비해 현재 탈것 상태를 다시 알려주는 처리를 예방 , 클라는 서버에서 주는 상태대로 클라이언트 상황을 맞춰 적용해줘야함 => 현재 위치에서 다음 목표 지점이 다이렉트로 나오는 방식
  • 길이 좁거나 막혀 있다면 탈것이 올 수가 없으니 이때는 제자리에서 바로 탈것을 타도록 한다 
  • 캐릭터가담당 서버가 바뀌는 지역으로 이동하는 경우 
    • 타려는 와중에 서버 이주가 진행 된다면 서버에서 바로 오르기 완료 상태로 바꾸고 클라이언트에 알려주는 상태로 처리 => 임의의 상태로 바로 전환되게 처리 하는 것이 맞아 떨어짐
  • 클라위치와 탈것이 만나지 못하는 거리로 탈것이 지나간다면 그때는 타지 못하고 지나가고  지나간 이후에 제자리에서 바로 탄 상태가 되도록 처리




  • 내리기 처리 : 빠르게 플레이로 돌아갈 수 있는데 집중
    • 오르기완료 상태라면 내리기 처리를 시작
    • 내리는 중, 내리기 완료 패킷을 서버에서 순차적으로 보내준다
      • 내리기과정은 특별히 진행 될게 없고 서버에서 보내주는대로 처리하기면 하면 됨
      • 내릴때 현재 바라보고 있는 방향으로 달려 나가면서 내리도록 처리하고
        위치도 탈것 상태를 전달하면서 같이 모든 클라에게 전달하여 모든 클라이언트에서 같은 곳이 될 수 있도록 처리
      • 내릴때는 탈때와의 반대 과정으로 처리 : 캐릭터와 탈것은 때어내면서 캐릭터 위치는 고정하고 이쪽으로 캐릭터가 이동 되게 하면서 애니메이션 재생
      • 내리는 중 패킷을 클라가 받은 것이라면 내리기 완료 패킷이 오는것은 확정적이라서 먼저 내려서 걸어가고 조작 될 수있게 처리 하면 자연스러워짐 
        예를 들어 말에서 바로 내리는 순간 공격이 가능하다



  • 곡선이동 자동이동 길잡이
    • 곡선이동 : 
    • 캐릭터의 이동은 기본적으로 직선이동이다, 이동 회전이 있긴 한데 이동 경로가 딱딱하다 , 이동회전은 느낌이 딱딱하다
    • 그래서 먼거리를 갈때는 
      먼저 큰 길을 찾아가고 목적지에 가까운 곳까지 큰길을 통해 이동하는 길 찾기 방식을 사용, 이러면 계속해서 큰길들의 분기점을 지나게 된다

 

 

  • 이 분기점을 지날때 급격히 방향이 꺽이는 경우들이 많다
  • 탈것을 타고 빠르게 지나갈때는 문제가 더 잘보이는데 이때 곡선
    형태로 이동 할 수 있는 것을 추가, 이 지역은 내비메쉬가 깔린 지역이다

  • 이런 점들에선 곡선형태로 분이점을 지나도록 처리
  • 베지어 곡선 사용 2차
    곡선이 항상 중심을 향해 들어오고 중심에서 가도록 하기위해 A1, B1 을 잡는데 각 직선에서 ¼ 지점정도에 만든다
    중간  지점을 만들기 위해 B0 지점을 임의로 만든다
  • 이때 샘플값이 균일한 곡선이 나오진 않는데
  • 이것은 시간이 아닌 이동거리를 통해 곡선의 이동 거리를 찾으면 됨
  • 2차 베지어 곡선은 특정 구간의 거리를 계산식 하나로 구할 수 있는 특성이 있다(이것이 다른 곡선과 달리 거리에 대한 이점이 있는 곡선이라 이걸 선택함) 
  • 거리를 통해 위치를 찾을때는 정확한 지점을 찾긴 어려우니 근사 값으로 찾음(뉴턴랩스방법) : 뉴턴법은 식조작으로 풀지못하는 식의해의 근사 값을 구하는 법으로 접선으로 구한다


  • 멀리서부터 접선을 그리다 보면 빨간점의 해와 접접 가까워지면서 해를 구하는 것 
  • 뉴턴방법 = 뉴턴-랩슨 방법  이라한다
    x0 에 해당하는 fx 의 접선을 구하고 이 접선과 만나는 x 축의 점 x1 에 대해 다시 기울기를 구해 이것을 반복하는 방식 으로 근사 해를 구하는 방식중 가장 빠르다 
  • 이것을 이용해서 거리기반의 위치를 구한다
    spline component는 에르미트 곡선을 사용한다
    에르미트 곡선은 거리에 따른 위치 값을 바로 얻어 올 수 있어서 이걸 쓰면




  • 자동길잡이 : 캐릭터의 이동 경로를 미리 보여주는 기능
    • 자동이동이 시작 되는 지점에서 이동해야 하는 경로가 대부분 결정되기 때문에
      이 경로를 미리 보여주는 것 이건 나한테만 보여지는 것으로 splinecomponent 를
      그대로 이용해서 표현이 가능하다

 

 

 

이글의 원문

프라시아 전기에 멋진 탈것 만들기
멋지게 타고, 멋지게 달리고, 멋지게 내리자

● 발표분야: 프로그래밍
● 발표자: 넥슨코리아 이연석 / Nexon Korea Yeonseok Yi
● 권장 대상: 게임 프로그래머, 클라이언트 프로그래머, UE4 프로그래머

반응형
반응형

#include 를통해 오래 걸리던 시간을 줄일 수 있는 대안이 된다

다른 랭귀지 C#, Java 등에서 사용 되었던 기법들인데 이제 c++ 에 들어왔다

사실 들어온진 좀 됐는데 실험 기능이였고 계속 발전 되었던 형태이긴 하다

 

 


 

모듈(module) 이란?

C++20 이전의 C++에서는 필요한 함수 또는 클래스를 불러오기 위해 #include 전처리문을 이용해 왔다. 이런 헤더 파일 방식의 문제는..많지만 그 중에 필자가 가장 크리티컬하게 생각하는 부분은 #include 전처리문을 가리키고 있던 파일의 내용 그대로 치환해버려 헤더에 있는 필요하든 필요하지 않든 상관 없이 정의 되어있는 모든 기능을 포함하게 된다는 것이다. 예를 들어 cmath 헤더 파일에서 정작 내가 필요한 기능은 acos함수 하나 뿐이지만, acos를 사용하기 위해서 나는 헤더에 정의된 모든 함수들을 인클루드하고 컴파일 해야만 한다.

이미 현재 다른 언어들에서는 필요한 기능만을 가져 올 수 있는 기능을 제공하고 있지만 C++은 이번 C++20 스펙의 module을 통해 필요한 것만 가져올 수 있는 기능을 제공한다.

기존 C++시스템과 module 도입 이후의 차이와 이점에 대해서 [여기]에 정리 되어 있으니 살펴 보도록 하자.

모듈 코드 작성하기

앞에서 알아 본바와 같이 모듈의 의도와 개념은 간단 명료하다. 이제 모듈을 사용하기 위해 필요한 것들을 알아 보자. 모듈을 사용하기 위해 우리가 알아야 할 것도 간단 명료하다.

module, import, export 이 세가지 키워드를 기억하자
  • module : 모듈의 이름을 지정
    eg) module Math : 모듈의 이름은 'Math'이다.
  • import : 가져올 모듈의 이름을 지정
    eg) import Math : 가져올 대상 모듈의 이름은 'Math'이다.
  • export : 모듈에서 내보낼 기능(함수)의 인터페이스를 지정
    eg) export int sum(int, int) : 내보낼 함수의 이름은 sum이고 리턴 타입은 int, 인자는 int, int다.

모듈 선언(declaration)

기존 #include 전처리 방식은 컴파일러와 상관 없이 선언은 헤더 파일에 위치하고 구현은 .cpp 파일 적성하는 방식이었다면 module은 각 컴파일러마다 각각 다른 방식으로 작성된다.

cl.exe(Microsoft 비주얼 스튜디오)

cl.exe는 비주얼 스튜디오의 컴파일러 이름이다. cl.exe는 모듈을 선언하기 위해 확장자가 ixx인 모듈 인터페이스 파일을 사용한다. 모듈을 만들기 위해 가장 먼저 할 일은 프로젝트에서 아래와 같이 'C++ 모듈 인터페이스 단위(.ixx)'를 선택하여 파일을 생성한다.

확장자가 .ixx인 모듈 인터페이스 파일을 만들고 내보낼 모듈의 이름과 인터페이스를 작성한다.

 
// ModuleA.ixx 모듈 인터페이스 파일

export module ModuleA;      // 내보낼 모듈의 이름 지정

namespace Foo
{
​​​​export int MyIntFunc()  // 모듈에서 내보낼 기능(함수)의 인터페이스를 지정
​​​​{
​​​​​​​​return 0;
​​​​}
​​​​
​​​​export double MyDoubleFunc()
​​​​{
​​​​​​​​return 0.0;
​​​​}
​​​​
​​​​void InternalMyFunc()   // 모듈 내부에서만 사용하는 함수
​​​​{
​​​​}
}
  • 3라인의 'export module ModuleA'는 우리가 함수나 변수를 선언하는 것과 같이 모듈을 만들겠다는 선언이다.
  • 7라인과 12라인의 export가 선언되어 있는 MyIntFunc()와 MyDoubleFunc()는 다른 파일에서 이 모듈을 임포트(import)했을 때 사용할 수 있는 함수라는 것을 의미한다.
  • 17라인의 InternalMyFunc() 함수는 모듈 내부에서만 사용되고 이 모듀을 임포트하는 곳에서는 사용하지 못한다.
  • 위 예제와 같이 선언과 구현을 ixx파일에 모두 작성할 수도 있고 다음 예제와 같이 기존 .h, .cpp 구조 처럼 나눠서 작성할 수도 있다.
 
// 선언만 있는 ModuleA.ixx 모듈 인터페이스 파일

export module ModuleA;      // 내보낼 모듈의 이름 지정

namespace Foo
{
​​​​export int MyIntFunc();
​​​​
​​​​export double MyDoubleFunc();
​​​​
​​​​void InternalMyFunc();
}
 
// ModuleA.cpp 모듈 구현 파일

module ModuleA;         // 시작 부분에 모듈 선언을 배치하여 파일 내용이 명명된 모듈(ModuleA)에 속하도록 지정

namespace Foo
{
​​​​int MyIntFunc()     // 구현에서는 export 키워드가 빠진다.
​​​​{
​​​​​​​​return 0;
​​​​}
​​​​
​​​​double MyDoubleFunc()
​​​​{
​​​​​​​​return 0.0;
​​​​}
​​​​
​​​​void InternalMyFunc()
​​​​{
​​​​}
}
Visual Studio 2019 버전 16.2에서는 모듈이 컴파일러에서 완전히 구현 되지 않았기 때문에 모듈을 사용하기 위해서는 /experimental:module 스위치를 활성화 해야 한다. 하지만 이 글을 적는 시점에서 최신버전 컴파일러는 모든 스펙을 다 구현하였으므로 모듈을 사용기 위해서는 최신 버전으로 업데이트할 필요가 있다.

module, import 및 export 선언은 C++20에서 사용할 수 있으며 C++20 컴파일러를 사용한다는 스위치 활성화가 필요하다(/std:c++latest ). 보다 자세한 사항은 [C++20] 컴파일 항목을 참고 하도록 하자.

참고로 필자의 경우 x86 32-bit 프로젝트를 생성해서 모듈을 사용하려고 했을 때 제대로 컴파일 되지 않았다. 비주얼 스튜디오에서 모듈을 사용하기 위해서는 64-bit 모드로 프로젝트를 설정해야 한다.

gcc

gcc의 경우 모듈을 선언하기 위해 .cpp 파일을 사용하지만 컴파일 시 -fmodules-ts 옵션을 이용해 컴파일 해야 한다. gcc에서 모듈을 사용하기 위해서는 gcc 버전 11이상이 필요하다. gcc 11의 설치 방법에 대해서는 [여기]를 참고 하도록 한다. gcc 컴파일에 대한 자세한 방법은 [여기]를 참고하자.

Global Module Fragment

적절하게 번역할 단어를 찾지 못해서 그대로 글로벌 모듈 프래그먼트(Global Module Fragment)라고 부르도록 하겠다. 글로벌 모듈 프래그먼트는 #include와 같은 전처리 지시자를 적어 주는 곳이라 생각하면 된다. 글로벌 모듈 프래그먼트는 익스포트(export)되지 않는다. 여기에 #include를 이용해 헤더를 불러왔다고 하더라도 모듈을 가져다 쓰는 곳에서 include의 내용를 볼 수 없다는 뜻이다.

 
// math1.ixx
module;                   // global module fragment (1)

#include <numeric>        // #include는 여기 들어간다
#include <vector>

export module math;       // module declaration (2)

export int add(int fir, int sec){
​​​​return fir + sec;
}

export int getProduct(const std::vector<int>& vec) {
​​​​return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
}

모듈 불러오기(import)

모듈을 불러와 사용하는 방법은 기존의 include와 매우 비슷하다. #include 대신 import 키워드를 사용한다

 
import ModuleA; // 모듈 임포트

#include <iostream>

int main()
{
​​​​std::cout << Foo::MyIntFunc() << std::endl;
​​​​return 0;
}

마치며

필자의 경우 비주얼 스튜디오에서는 #include, import 순서에 상관 없이 정상적으로 컴파일 되었지만, gcc에선 헤더와 모듈의 순서에 따라 컴파일 에러가 발생했었다(내가 뭔가를 잘못해서 그럴 수도 있지만..). 모듈을 임포트 후 shared_ptr을 위해 memory 헤더를 인클루드하면 컴파일 오류가 발생하고, 헤더를 먼저 인클루드 후 모듈을 임포트하면 정상적으로 컴파일 되었다. 

 
// compile : g++ -std=c++20 -fmodules-ts hello.cc main.cc

// import hello;      // Compile Error!!
// #include <memory>

#include <memory>     // Compile Success
import hello;

int main()
{
​​​​std::shared_ptr<int> i = std::shared_ptr<int>(new int);
​​​​greeter("world");
​​​​return 0;
}

위 예제의 전체 프로젝트 코드는 [여기]에서 확인 할 수 있다.

 

 

ref : https://kukuta.tistory.com/389

반응형

반응형

언리얼 에서 델리게이트는 C++ 객체에만 사용할 수 있는 델리게이트와 C++, 블루프린트 객체가 모두 사용할 수 있는 델리게이트로 나뉜다. 블루프린트 오브젝트는 멤버 함수에 대한 정보를 저장하고 로딩하는 직렬화 매커니즘이 들어있기 때문에 일반 C++ 언어가 관리하는 방법으로 멤버 함수를 관리 할수 없다. 

그래서 블루프린트와 관련된 C++함수는 모두 UFUNCTION 매크로를 사용해야 한다.
이렇게 블루프린트 객체와도 연동하는 델리게이트를 언리얼 엔진에서는 다이내믹 델리게이트라고 한다.

 

 

 

 


Delegate란?

함수를 바인딩하는 형태로 등록시켜 CallBack함수와 같이 활용 할 수 있습니다.

 

언리얼 C++에서 충돌감지 컴포넌트 개열에서 AddDynamic 메크로를 통해 출돌시 등록한 CallBack함수를 호출하는 것이 구현 되어 있습니다.

 

언리얼 C++에는 총 4가지 종류의 Delegate가 있습니다.

델리게이트(싱글 케스트) 가장 기본적인 Delegate로 함수 1개를 바인드하여 사용합니다.
멀티 케스트 싱글 케스트와 동일하지만 여러 함수를 바인드 할 수 있습니다.
이벤트 멀티 케스트와 동일하지만 전역으로 설정할 수 없어 외부 클래스에서 추가 델리게이트 선언이 불가능합니다.
다이나믹 멀티케스트(다이나믹) 다이나믹은 싱글과, 멀티 두개다 존재하며 다이나믹 델리게이트는 직렬화(Serialize)화 되어 블루프린트에서 사용 가능합니다.

 

바인딩(Bind)란 Delegate에 특정 바인드 함수를 통해 콜백함수를 등록하는 것을 의미합니다.

공식문서에는 여러 형태의 바인드함수가 있지만 이 포스팅에서는 함수 바인딩을 중심적으로 설명하겠습니다.

 

 

 


샘플 프로젝트 소개

샘플 프로젝트는 포스팅 상단에 업로드했습니다.

 

샘플 프로젝트는 폭탄 오브젝트가 하나 있으며 키보드 "B"키는 누르면 점화되어 3초 뒤에 폭발합니다.

프로젝트의 구현 형태는 폭탄 클래스에다 함수들을 바인드시켜놓고 폭탄이 터지면 바인드한 함수들을 호출하여

 

출력 로그에 해당 함수 내에서 로그 코드가 발동되게 구현했습니다.

 

 

 


기본적인 Delegate 세팅법은 먼저 싱글케스트를 예시로 먼저 설명하겠습니다.

 

헤더 파일 설명

//! ABoom.h

#include "Boom.generated.h"

//! SingleCast
DECLARE_DELEGATE(FDele_Single); 
DECLARE_DELEGATE_OneParam(FDele_Single_OneParam, int32);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()

........

public :
	FDele_Single Fuc_DeleSingle;
	FDele_Single_OneParam Fuc_DeleSingle_OneParam;
};

 

헤더 파일에서 Delegate함수를 만들고 DECLARE_DELEGATE()메크로를 통해 델리게이트화 시킵니다.

 

DECLARE_DELEGATE는 인자 값 없는 함수에 사용하고 DECLARE_DELEGATE_OneParam는 1개의 인자값이 있는 함수에 사용합니다, 만약 인자 값이 2개면 TwoParam을 사용합니다.

 

델리게이트는 자료형 이름 앞에 "F"를 붙여야합니다.

안 그러면 위 에러가 뜨게 됩니다.

 

 

Cpp 파일 설명

//! ABoom.cpp

void ABoom::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//! 델리게이트 해제
	Fuc_DeleSingle.Unbind();
	Fuc_DeleSingle_OneParam.Unbind();
}

void ABoom::Tick_Boom(float DeltaTime)
{
		//! Delegate호출하는 부분
		if(Fuc_DeleSingle.IsBound()==true)	Fuc_DeleSingle.Execute();
		if(Fuc_DeleSingle_OneParam.IsBound() == true) Fuc_DeleSingle_OneParam.Execute(123);
}

 

EndPlay함수에 Unbind()함수는 일종의 메모리 해제 함수입니다, 해당 델리게이트에 바인드된 함수를 제거합니다.

 

실제 델리게이트에 등록된 Callback 함수를 호출하는 부분입니다.

IsBound()를 통해 바인드가 되어있는지 확인하고 리턴값이 True이면 Execute()함수를 통해 호출합니다.

인자 값이 있는 함수는 Execute함수의 인자 값으로 입력합니다.

 

 

 


Delegate에 함수 등록 & 싱글케스트(Single cast)

이 챕터는 Delegate가 세팅된 ABoom 클래스에 ATestPlayer클래스의 함수를 등록하는 과정입니다.

 

//! ATestPlayer.h

UCLASS()
class DELEGATETEST_API ATestPlayer : public AActor
{
	GENERATED_BODY()
public:	
	//! Delegate에 의해 호출될 함수

	UFUNCTION()
		void CallDeleFunc_Single();
	UFUNCTION()
		void CallDeleFunc_Single_OneParam(int32 nValue);

먼저 ABoom클래스에 등록할 함수는 인자값에 맞춰 함수를 만듭니다.

Delegate에 등록할 함수는 UFUNCTION()메크로를 붙여야 합니다.

 

//! ATestPlayer.cpp

//! Delegate에 의해 호출될 함수
void ATestPlayer::CallDeleFunc_Single()
{
	UE_LOG(LogTemp, Warning, TEXT("CallDeleFunc_Single"));
}

void ATestPlayer::CallDeleFunc_Single_OneParam(int32 nValue)
{
	UE_LOG(LogTemp, Warning, TEXT("CallDeleFunc_Single_OneParam / %d"), nValue);
}

해당 함수에 단순하게 로그를 출력하는 코드를 작성했습니다.

 

//! ATestPlayer.cpp

void ATestPlayer::BeginPlay()
{
	m_pBoom->Fuc_DeleSingle.BindUFunction(this, FName("CallDeleFunc_Single"));
	m_pBoom->Fuc_DeleSingle_OneParam.BindUFunction(this, FName("CallDeleFunc_Single_OneParam"));
}

함수를 바인드하는 코드입니다, ABoom.h에서 만든 Delegate 변수에 접근하여 함수를 바인드하는 함수를 통해 함수를 등록합니다.

 

 

이로서 Delegate의 기본 형태인 싱글케스트 구현이 완료됬으며, 폭탄이 터지면 로그가 출력될 것입니다.

 

 


멀티 케스트(Multi cast)

멀티케스트가 델리게이트(싱글케스트)와 차이점은 함수를 여러개 바인드 할 수 있습니다.

 

//! ABoom.h

//! MultiCast
DECLARE_MULTICAST_DELEGATE(FDele_Multi);
DECLARE_MULTICAST_DELEGATE_OneParam(FDele_Multi_OneParam, int32);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	
	FDele_Multi Fuc_DeleMulti;
	FDele_Multi_OneParam Fuc_DeleMulti_OneParam;
};

멀티 케스트는 싱글케스트와 비슷하게 헤더파일에서 작성됩니다.

 

//! ABoom.cpp

void ABoom::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//! 델리게이트 해제
	Fuc_DeleMulti.Clear();
	Fuc_DeleMulti_OneParam.Clear();
}

void ABoom::Tick_Boom(float DeltaTime)
{
		if(Fuc_DeleMulti.IsBound() == true) Fuc_DeleMulti.Broadcast();
		if(Fuc_DeleMulti_OneParam.IsBound() == true) Fuc_DeleMulti_OneParam.Broadcast(456);
}

cpp파일입니다.

 

 

싱글케스트와 차이점은 Unbind가 Clear로 되고 Execute가 Broadcast로 바뀌었습니다.

 

 

//! ATestPlayer.cpp

void ATestPlayer::BeginPlay()
{
	Super::BeginPlay();

		//! SingleCast
		m_pBoom->Fuc_DeleSingle.BindUFunction(this, FName("CallDeleFunc_Single"));
		m_pBoom->Fuc_DeleSingle_OneParam.BindUFunction(this, FName("CallDeleFunc_Single_OneParam"));

		//! MultiCast
		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_1"));
		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_2"));
        m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_1"));
		m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_2"));

함수를 등록하는 부분입니다, 싱글케스트와 쓰임세는 비슷하지만 함수는 BindUFunction->AddUFunction으로 변경되었습니다.

 

결과를 확인하면 폭탄이 터질때 해당 델리게이트에 .BroadCast()함수를 한번 호출하면 안에 등록된 모든 함수가 호출이 되는 것을 확인 할 수 있습니다.

 

없으면 아무것도 실행 안됨


이벤트(Event)

이벤트는 멀티 케스트와 유사하지만 전역으로 설정할 수 없어 외부 클래스에서 추가 델리게이트 선언이 불가능합니다.

캡슐화 개념으로 사용하면 될것 같습니다.

 

//! ABoom.h

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	//! Event
	DECLARE_EVENT(ABoom, FDele_Event);
	DECLARE_EVENT_OneParam(ABoom, FDele_Event_OneParam, int32);
    
public :
	FDele_Event Fuc_DeleEvent;
	FDele_Event_OneParam Fuc_DeleEvent_OneParam;

전역에 작성했던 이전 Delegate와 달리 Event는 클래스 내부에 작성하고 인자값으로 주인클래스의 클래스를 입력합니다.

 

이외에는 이전 멀티케스트와 사용법이 동일합니다.

 

 

 


다이나믹 멀티캐스트 (다이나믹(Dynamic))

다이나믹 델리게이트는 직렬화(Serialize)화 되어 블루프린트에서 사용 가능합니다.

 

//! ABoom.h

//Dynamic
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDele_Dynamic);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDele_Dynamic_OneParam, int32, SomeParameter);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic Fuc_Dynamic;

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic_OneParam Fuc_Dynamic_OneParam;

이전 델리게이트와 비슷하지만 변수 작성부분에서 BlueprintAssignable 등 UPROPERTY 메크로를 작성합니다.

 

해당 포스팅에서는 DYNAMIC_MULTICAST만 사용했습니다,

 

 

다이나믹 델리게이트는 직렬화(Serialize)화가 가능하고 UPROPERTY 메크로를 통해서 블루프린트에서 델리게이트 바인드가 가능합니다.

이 기능은 블루프린트 디스패처랑 비슷하게 사용이 가능합니다.

 

그리고 Cpp에서는 이전 멀티케스트와 동일하게 사용합니다.

 

 

//! ATestPlayer.cpp

void ATestPlayer::BeginPlay()
{
	Super::BeginPlay();
    
	m_pBoom->Fuc_Dynamic.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic);
	m_pBoom->Fuc_Dynamic_OneParam.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic_OneParam);

다이나믹 델리게이트는 이전과는 다르게 위 .AddDynamic 메크로함수로 바인드를 합니다.

 

 

폭탄이 터지고 로그를 확인하면 블루프린트에서 바인드한 Custom함수와, C++에서 바인드한 함수가 동시에 호출되는 것을 확인 할 수 있습니다.

 

 

 


핵심 코드

 

1. 델리게이트 세팅

//! SingleCast
DECLARE_DELEGATE(FDele_Single);
DECLARE_DELEGATE_OneParam(FDele_Single_OneParam, int32);

//! MultiCast
DECLARE_MULTICAST_DELEGATE(FDele_Multi);
DECLARE_MULTICAST_DELEGATE_OneParam(FDele_Multi_OneParam, int32);

//Dynamic
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDele_Dynamic);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDele_Dynamic_OneParam, int32, SomeParameter);

UCLASS()
class DELEGATETEST_API ABoom : public AActor
{
	GENERATED_BODY()
public :
	//! Event
	DECLARE_EVENT(ABoom, FDele_Event);
	DECLARE_EVENT_OneParam(ABoom, FDele_Event_OneParam, int32);
    
    
 //---------------------------------------------------------------------
 
 public :
	FDele_Single Fuc_DeleSingle;
	FDele_Single_OneParam Fuc_DeleSingle_OneParam;

	FDele_Multi Fuc_DeleMulti;
	FDele_Multi_OneParam Fuc_DeleMulti_OneParam;

	FDele_Event Fuc_DeleEvent;
	FDele_Event_OneParam Fuc_DeleEvent_OneParam;

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic Fuc_Dynamic;

	UPROPERTY(BlueprintAssignable, VisibleAnywhere, BlueprintCallable, Category = "Event")
		FDele_Dynamic_OneParam Fuc_Dynamic_OneParam;

 

2. 델리게이트에 바인드된 함수 호출 & 해제

void ABoom::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	//! 델리게이트 해제

	Fuc_DeleSingle.Unbind();
	Fuc_DeleSingle_OneParam.Unbind();

	Fuc_DeleMulti.Clear();
	Fuc_DeleMulti_OneParam.Clear();

	Fuc_DeleEvent.Clear();
	Fuc_DeleEvent_OneParam.Clear();

	Fuc_Dynamic.Clear();
	Fuc_Dynamic_OneParam.Clear();
}

void ABoom::Tick_Boom(float DeltaTime)
{
		//! Delegate호출하는 부분
		if(Fuc_DeleSingle.IsBound()==true)	Fuc_DeleSingle.Execute();
		if(Fuc_DeleSingle_OneParam.IsBound() == true) Fuc_DeleSingle_OneParam.Execute(123);
		if(Fuc_DeleMulti.IsBound() == true) Fuc_DeleMulti.Broadcast();
		if(Fuc_DeleMulti_OneParam.IsBound() == true) Fuc_DeleMulti_OneParam.Broadcast(456);
		if(Fuc_DeleEvent.IsBound() == true) Fuc_DeleEvent.Broadcast();
		if(Fuc_DeleEvent_OneParam.IsBound() == true) Fuc_DeleEvent_OneParam.Broadcast(789);
		if (Fuc_Dynamic.IsBound() == true) Fuc_Dynamic.Broadcast();
		if (Fuc_Dynamic_OneParam.IsBound() == true) Fuc_Dynamic_OneParam.Broadcast(999);
}

 

3. 델리게이트 함수 바인드

void ATestPlayer::BeginPlay()
{
	Super::BeginPlay();

	//! Delegate 등록하기
	if (m_pBoom != nullptr)
	{
		m_pBoom->Fuc_DeleSingle.BindUFunction(this, FName("CallDeleFunc_Single"));
		m_pBoom->Fuc_DeleSingle_OneParam.BindUFunction(this, FName("CallDeleFunc_Single_OneParam"));

		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_1"));
		m_pBoom->Fuc_DeleMulti.AddUFunction(this, FName("CallDeleFunc_Multi_2"));

		m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_1"));
		m_pBoom->Fuc_DeleMulti_OneParam.AddUFunction(this, FName("CallDeleFunc_Multi_OneParam_2"));
		
		m_pBoom->Fuc_DeleEvent.AddUFunction(this, FName("CallDeleFunc_Event"));
		m_pBoom->Fuc_DeleEvent_OneParam.AddUFunction(this, FName("CallDeleFunc_Event_OneParam"));

		m_pBoom->Fuc_Dynamic.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic);
		m_pBoom->Fuc_Dynamic_OneParam.AddDynamic(this, &ATestPlayer::CallDeleFunc_Dynamic_OneParam);
	}
}

 

 

https://darkcatgame.tistory.com/66

반응형
반응형

언리얼엔진하면은 다룬다고 하면 알아야할 몇가지들이 있다. 그 중에서 언리얼엔진에서 베이스가 되는 프로퍼티 시스템인 리플렉션 이 있다.

 

 리플렉션이라는 것은 자바나 C#등에선 지원하지만, 언리얼엔진에서 사용되는 C++ 언어에서는 지원하지 않아 언리얼엔진에서 구현되어 있는 시스템이라고 했다.

 

하지만 난 리플렉션이라는게 뭐길래 엔진에서 구현해서 지원해주는것일까??  처음들어봐서 자바에서 어떻게 사용되는지 먼저 찾아보았다.

 

 

 

 

자바에서의 리플렉션 개념

자바에서의 리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다. 
(투영, 반사 라는 사전적인 의미를 지니고 있다.)

자바의 Reflection은 JVM에서 실행되는 애플리케이션의 런타임 동작을 검사하거나 수정할 수 있는 기능이 필요한 프로그램에서 사용됩니다. 쉽게 말하자면, 클래스의 구조를 개발자가 확인할 수 있고, 값을 가져오거나 메소드를 호출하는데 사용됩니다.

 

간단히 요약하자면, 컴파일 시간이 아니라 런타임시간에 동적으로 특정 클래스의 정보 객체화를 통해 분석 및 추출해낼수 있는 프로그래밍 기법이라고 표현할수 있다. (개발 과정에서 개발자에게 조금 더 도움이 되는 시스템이라고 다가왔다.)

 

 

 

 

 

언리얼 엔진에서의 리플렉션 

이런 좋은 시스템이 C++에는 지원하지 않아, 언리얼 엔진에서 구현되어 있는데, 우선  관련 설명을 찾아보면

리플렉션(Reflection)은 프로그램이 실행시간에 자기 자신을 조사하는 기능입니다.

이는 엄청나게 유용한 데다 언리얼 엔진 테크놀로지의 근간을 이루는 것으로, 에디터의 디테일 패널, 시리얼라이제이션, 가비지 콜렉션, 네트워크 리플리케이션, 블루프린트/C++ 커뮤니케이션 등 다수의 시스템에 탑재된 것입니다.


언리얼엔진 홈페이지 중...

위에서 '자기 자신'은 클래스, 구조체, 함수, 멤버 변수, 열거형 등을 의미하고 있다.

수집 된 정보는 UClass에 보관된다. 런타임에는 GetClass() 함수를 통해 접근하고, 컴파일 타임에서는 StaticClass()를 사용해 접근한다. 해당 함수들은 언리얼 헤더 툴에 의해 자동으로 생성된다.

 

 

 

 

 

잠깐, 왜 언리얼에서는 프로퍼티 시스템 이라고도 하는가?

전형적으로 이러한 리플렉션은 '프로퍼티 시스템'이라고 부르는데,그 이유로는 아마도 '리플렉션(Reflection/반사)'은 그래픽 용어라서 혼돈을 빚을 수 있기 때문인것 같다.(개인적인 추측)

 

 

리플렉션을 알려주는 방법?

 Unreal Header Tool (UHT)가 그 프로젝트를 컴파일할 때 해당 정보를 수집한다.

헤더(.h)에 리플렉션이 있는 유형으로 알려주려면, 헤더 파일 상단에 특수한 include 를 추가해 줘야 하야한다.

 그러면 리플렉션이 있는 유형은 이 파일을 고려해야 한다는것 그리고 시스템 구현에도 필요함을 UHT 에 알려줍니다.

그 #include는 아래와 같다.

#include "FileName.generated.h"

 

위와 같은 헤더가 추가 된다면, 이제 열겨형/UENUM(), 클래스/UCLASS(), 구조체/USTRUCT(), 함수/UFUNCTION(), 멤버변수/UPROPERTY() 를 사용하여 헤더의 다양한 유형과 멤버 변수 주석을 달 수 있다.

이 매크로 각각은 유형 및 멤버 선언 전에 오며, 추가적인 지정자 키워드를 담을 수 있습니다. 

 

 

 

 

 

리플렉션작동 원리

UBT(Unreal Build Tool) 는 그 역할을 위해 헤더를 스캔한 다음 리플렉션된 유형이 최소 하나 있는 헤더가 들어있는 모듈을 기억.

그 헤더 중 어떤 것이든 지난 번 컴파일 이후 변경되었다면, UHT 를 실행하여 리플렉션 데이터를 수집하고 업데이트.

UHT 는 헤더를 파싱하고, 리플렉션 데이터 세트를 빌드한 다음, (모듈별.generated.inl 에 기여하는) 리플렉션 데이터가 들어있는 C++ 코드를 생성할 뿐만 아니라, (헤더별 .generated.h 인) 다양한 헬퍼 및 thunk 함수도 생성.

 

요약 : 

1. UBT가 리플렉션 키워드 탐색

2. UHT가 해당 .cpp를 파싱

3. 리플렉션 데이터 정보를 수집

4. 수집된 정보는 별개의 C++ 코드 .generated.h / .cpp로 저장

5. 빌드 시 기존 코드에 .generated.h 코드를 추가해 컴파일

 

 

이렇게 UHT에 수집된 리플렉션 데이터는 별개의 코드로 저장되고 클래스이름.generated가 붙는다.

언리얼 실행환경은 이를 사용해 언리얼 오브젝트를 관리하고 에디터에서는 에디터에서 편집할 수 있는 인터페이스를 제공한다.

해당 파일들을 프로젝트 폴더에서 \Intermediate 폴더에 저장 된다. 클래스.h의 내용이 변경될때마다 자동 생성, 기존 파일을 덮어 쓴다.

 

 

 

generated 함수에는 StaticClass() / StaticStruct() 같은 것이 포함되어 있어, 유형에 대한 리플렉션 데이터를 구하는 것이 쉬워질 뿐만 아니라, 블루프린트나 네트워크 리플리케이션에서 C++ 함수를 호출하는 데 사용되는 thunk 를 구하는 것도 쉬워집니다.  이는 클래스나 구조체의 일부로 선언되어야 하며, GENERATED_UCLASS_BODY() 또는 GENERATED_USTRUCT_BODY() 매크로가 리플렉션된 유형에 포함되어야 하는지에 대한 이유가 됩니다.

이 매크로를 정의하는 #include 'TypeName.generated.h' 는 물론입니다.

 

 

 

ref : https://hyo-ue4study.tistory.com/182

 

반응형
반응형

According to the following test, it seems that a std::vector<int> increases its capacity in this way:

  • it happens when we push_back() and the capacity is already full (i.e. v.size() == v.capacity()), it has to be noted that it doesn't happen a little bit before
  • the capacity increases to 1.5 times the previous capacity

Question: why this 1.5 factor? Is it implementation-dependent? Is it optimal?

Also, is there a way to analyze, in this code, when exactly a reallocation happens? (sometimes maybe the capacity can be increased without moving the first part of the array)

 

Note: I'm using VC++ 2013

 

 

 

I think an important aspect to the answer of why the factor is 1.5 is preferred over 2.0, is that the standard c++ allocator model does not support a realloc function:

Realloc- in Standard allocator

If it would exist, and it would be used in std::vector, the mentioned memory fragmentation problem would be minimized in all cases where the factor 1.5 is better compared to e.g. factor 2.0 capacity increase.

The factor of 1.5 (or more precisely the golden ratio) is just optimum when the buffer is always relocated on every buffer size increase (as the other answers explain).

With realloc and if there is space after the current memory block, it would just be enlarged without copying. If there is no space, realloc call would of course move the datablock. However then the free'd blocks (if this happens multiple times) would not be contiguous anyway. Then it doesnt matter whether 1.5 or 2 is the factor for buffer enlargement... Memory is fragmented anyways.

So a realloc approach in std::vector would help wherever the factor 1.5 has the advantage over 2.0 and would save the data copy.

 

 

 

ref : https://stackoverflow.com/questions/45403052/when-does-a-stdvector-enlarge-itself-when-we-push-back-elements

반응형
반응형

3 way handshake

TCP는 장치들 사이에 논리적인 접속을 성립시키기 위해 3 way handshake를 사용한다.

TCP 3 way handshake(이하 3way)는 TCP/IP 프로토콜을 이용해서 통신을 하는 응용프로그램이

데이터를 전송하기 전에 먼저 정확한 전송을 보장하기 위해 상대방 컴퓨터와 사전에 세션을 수립하는 과정을 의미한다.

TCP 통신은 PAR(Positive Acknowledgement with Re-transmission)을 통해 신뢰적인 통신을 제공한다.

PAR을 사용하는 기기는 ack을 받을 때까지 데이터 유닛을 재전송한다.

수신자가 데이터 유닛(세그먼트)이 손상된 것을 확인하면 해당 세그먼트를 없앤다.

그러면 sender는 positive ack이 오지 않은 데이터 유닛을 다시 보내야 한다.

작동방식

HOST P와 HOST Q가 있을 때, HOST P가 클라이언트, HOST Q가 서버 라고 가정해보자.

 
  1. 클라이언트가 서버에게 SYN 패킷을 보냄.Client > Server : TCP SYN
  2. 서버가 SYN(x)을 받고 클라이언트로 받았다는 신호 ACK(x + 1)와 SYN(y)패킷을 보냄.Server > Client : TCP SYN, ACK
  3. 클라이언트는 서버의 응답(ACK(x+1), SYN(y))을 받고, ACK(y+1)를 서버로 보냄.Client > Server : TCP ACK
  • SYN : 'Synchronize sequence numbers', 연결 요청. 세션을 설정하는데 사용되며 초기에 시퀀스 번호를 보냄.
  • ACK : 'Acknowledgement', 보낸 시퀀스 번호에 TCP 계층에서의 길이 또는 양을 더한 것과 같은 값을 ACK에 포함하여 전송.

동기화 요청에 대한 답변: Client의 Sequence Number + 1을 하여 ACK로 돌려준다.

이렇게 3번의 통신이 완료되면 연결이 성립된다.

SYN을 보내면 ACK를 받을 때 까지 재전송한다. ACK가 와야 완료된다는 뜻이다.

이러한 행위로 양쪽 모두 데이터를 전송할 준비가 되었다는 것을 보장하고,

실제로 데이터 전달이 시작하기 전에 한쪽이 다른 쪽이 준비되었다는 것을 알 수 있도록 한다.


4 way handshake

4 way handshake는 연결을 해제하는 과정이다. 여기서는 FIN 플래그를 이용한다.

  • FIN : 세션을 종료시키는데 사용되며, 더 이상 보낸 데이터가 없음을 나타낸다.

Termination의 종류

TCP는 두 가지 연결 해제 방식이 있다.

  1. Graceful connection release(정상적인 연결 해제)

정상적인 연결 해제에서는 양쪽이 커넥션을 모두 닫을 때까지 연결되어 있다.

  1. Abrupt connection release(갑작스런 연결 해제)
    1. 갑자기 한 TCP 엔티티가 연결을 강제로 닫는 경우
    2. 한 사용자가 두 데이터 전송 방향을 모두 닫는 경우

Abrupt

RST(TCP reset) 세그먼트가 전송되면 갑작스러운 연결 해제가 수행되는데, RST 세그먼트는 다음과 같은 경우에 전송된다.

  1. 존재하지 않는 TCP 연결에 대해 비 SYN 세그먼트가 수신된 경우
  2. 열린 커넥션에서 일부 TCP 구현이 잘못된 헤더가 있는 세그먼트가 수신된 경우
    • RST 세그먼트를 보내, 해당 커넥션을 닫아 공격을 방지한다.
  3. 일부 구현에서 기존 TCP 연결을 종료해야 하는 경우

Graceful

연결 종료 요청 역시, 요청을 먼저 시도한 요청자를 Client로, 요청을 받은 수신자를 Server 쪽으로 생각하면 된다.

 
  1. 클라이언트는 서버에게 연결을 종료한다는 FIN 플래그 보냄.
  2. 서버는 FIN을 받고, 확인했다는 ACK를 클라이언트에게 보낸다. 이 때, 모든 데이터를 보내기 위해 CLOSE_WAIT 상태가 된다.
  3. 데이터를 모두 보냈다면, 연결이 종료되었다는 FIN 플래그를 클라이언트에게 보낸다.
  4. 클라이언트는 FIN을 받고, 확인했다는 ACK를 서버에게 보낸다. 이 때 아직 서버로부터 받지 못한 데이터가 있을 수 있으므로 TIME_WAIT을 통해 기다린다.
    1. 서버는 ACK를 받고, 소켓을 닫는다(Closed)
    2. TIME_WAIT 시간이 끝나면 클라이언트도 닫는다 (Closed) => 의도치 않은 에러로 연결이 데드락으로 빠지는 것을 방지한다.

이렇게 4번의 통신이 완료되면 연결이 해제된다.

 

 

ref : https://mirror.xyz/0xA1d9f681B25C14C1eE7B87f1CF102E73cA3ad4d9/l3J2_t_85JfypzGWRdgX-qMYW7nob6XIwi1BkXs4Gm4

반응형
반응형

 

 

Damage is a common concept in video games. Because of this, one of the default features in Unreal Engine is a framework for dealing and receiving damage.

This tutorial is the first in a series about damage, and aims to give you an introduction to the Damage system in blueprint and C++.

Damage in UnrealPermalink

To borrow a phrase from Alex Forsythe: With Unreal, damage comes standard. It’s a feature that is part of the Actor class, meaning that every actor class that you’ve created so far already natively supports dealing & receiving damage!

Let’s start this tutorial with a quick introduction to the concept of instigator. After that, we’ll look into how damage should be dealt and received, both in blueprint and C++.

InstigatorPermalink

If you’ve been working in Unreal, you might already have seen the term “instigator” a couple of times. Every actor has an Instigator property, it is a reference to a Pawn and represents the pawn/character responsible for any actions the spawned actor will do, like dealing damage. A very practical example is a cannonball, the instigator would be the pawn firing the cannon.

The instigator is usually set when you spawn the actor. In blueprints, this is done like this:

In C++, you can set the instigator pawn in the FActorSpawnParameters struct:

void AYourPawn::SpawnProjectile(const TSubclassOf<AActor> ProjectileClass)
{
    // FP_MuzzleLocation must be a valid component.
    check(FP_MuzzleLocation != nullptr);

    // Spawn a projectile at a specified transform, with instigator set to this pawn.
    const FTransform& SpawnTransform = FP_MuzzleLocation->GetComponentTransform();
    FActorSpawnParameters SpawnParams;
    SpawnParams.Instigator = this;
    World->SpawnActor<AActor>(ProjectileClass, SpawnTransform, SpawnParams);
}

It is also possible to change the instigator after spawning by simply calling SetInstigator.

The concept of instigators is incredibly useful for damage: It allows a damage receiver to act depending on who was responsible for dealing the damage. The classroom example for this is friendly fire: If you receive damage and the instigator of the damage is someone on your own team, you might decide to ignore the damage if friendly fire is disabled.

Dealing damagePermalink

To inflict damage on an actor with blueprints, simply call the ApplyDamage function:

Let’s briefly talk about some of the parameters:

  • Damaged Actor: The actor you’re dealing damage to.
  • Event Instigator: The controller that is responsible for dealing this damage. You can obtain the instigator controller using the GetInstigatorController node.
  • Damage Causer: The actor that actually dealt this damage (e.g. a projectile or grenade).
  • Damage Type Class: An object that can influence damage calculations. This deserves a tutorial on it’s own so we will just keep it empty for now.

In addition to ApplyDamage, there are also separate, specialized functions for dealing point and radial damage. Point damage is for damage at a particular point, coming from a particular direction (like being hit with a bullet). Radial damage is for damage in a wider area, possibily affecting multiple actors (like a grenade blast). More about point and radial damage in a future tutorial.

The C++ equivalent for dealing damage is AActor::TakeDamage. While the blueprint equivalent has different functions for each damage variant (i.e. point, radius, etc.), the TakeDamage function combines all of these into one with a FDamageEvent struct to differentiate between the variants.

Dealing damage in C++ is pretty simple though, here is the equivalent for the above blueprint example:

void AYourActor::DealDamageTo(class AActor* OtherActor)
{
    // Much like the blueprint example, we're not using DamageType yet.
    OtherActor->TakeDamage(4.2f, FDamageEvent(), GetInstigatorController(), this);
}

For dealing point and radial damage, you will need to supply a FPointDamageEvent or FRadialDamageEvent with the proper arguments, instead of the FDamageEvent. For applying radial damage, I recommend using the UGameplayStatics::ApplyRadialDamage helper function instead.

Responding to damagePermalink

Responding to damage in an actor is as simple as overriding the AnyDamage function in blueprint. For C++ things are a bit more tricky, we’ll get to that after the blueprint stuff.

In addition to the any damage function, there are also separate functions for receiving point and radial damage. Keep in mind that when you receive point or radial damage, AnyDamage will still get called!

The following is an example of responding to damage blueprint, simply override the AnyDamage function:

Most of the function arguments here are the same as the values you provided to the ApplyDamage function. The only exception is Damage Type, which is now an object reference rather than a class reference, more info on this in a future tutorial.

In addition to AnyDamage, there is also the OnTakeAnyDamage event dispatcher. This event is particularly useful if you have a component that takes care of dealing with damage, rather than the actor itself:

Unfortunately, the C++ function ReceiveAnyDamage in AActor was not made virtual by Epic, so it cannot be overridden. The next best way is to override the TakeDamage function instead (InternalTakeRadialDamage and InternalTakePointDamage exist for radial and point damage respectively):

float AYourActor::TakeDamage(const float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    // If you need the DamageType object like in blueprint, this is how you do it:
    UDamageType const* const DamageTypeCDO = DamageEvent.DamageTypeClass ? DamageEvent.DamageTypeClass->GetDefaultObject<UDamageType>() : GetDefault<UDamageType>();

    // Make sure to call Super so blueprint & event dispatchers still fire.
    return Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
}

Fortunately, the OnTakeAnyDamage delegate can simply be bound to in C++, just like with blueprint:

void UYourActorComponent::BeginPlay()
{
    Super::BeginPlay();

    // Subscribe to the owner receiving damage event.
    GetOwner()->OnTakeAnyDamage.AddDynamic(this, &UYourActorComponent::OnTakeRadialDamage);
}

void UYourActorComponent::OnTakeRadialDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
{
    // Your damage handling code here..
}

Conclusion

This tutorial has covered the basics of damage: It introduced the concept instigators, how damage should be dealt and how it should be received, both in blueprint and C++.

 

 

 

 

 

 

https://jasperdelaat.com/unreal-engine/damage-1/

반응형

'게임엔진(GameEngine) > Unreal4' 카테고리의 다른 글

[UE] 델리게이트 종류와 차이  (0) 2023.03.08
리플렉션  (0) 2023.03.07
애니메이션 디스턴스매칭  (0) 2023.02.20
TSharedPtr<.... , ESPMode::ThreadSafe> 스레드 안정성  (0) 2023.01.20
Widget for painting  (0) 2022.11.23
반응형

Clustered 는 Leaf Page 에 데이터가 들어가고 이전까지 보던 방식
Non-Clustered 같은 경우는 RID 를 통해 한번 경유하여 데이터를 저장하는 방식이다

Clustered 영한사전 , Non-Clustered 색인

Clustered : 실제로 논리적으로 정렬 되는 데이터 순서가 키 값 정렬 순서가 된다
Leaf Page = Data Page 끝 노드에 실제 데이터가 들어가는 형태
    데이터는 clustered index 키 순서로 정렬

 

 Non-Clustered 는 clustered index 유무에 따라서 다르게 동작한다

  1.  Non-Clustered 인데 clustered index 가 없는 경우(추가 안한 경우)
    leaf page 에는 heap table 에 대한 heap rid가 존재하여 이 rid 로 -> Heap table 에 접근 데이터 얻어온다
    >  heap rid = Heap table 에서 어느 주소에 몇번째 슬롯에 데이터가 있는지를 알 수 있는 RID
    >  데이터는 heap table 이 있고 이곳에 데이터들이 저장된다
        (즉 leaf Page 가 인덱스 페이지가 아니고 데이터 페이지가 아니라는 얘기)

  2. Non-Clustered 가 키가 있는데 이때  clustered index 를 추가 하게 되면
    heap table 이 없고 leaf page 에 데이터가 있는 상태이다 => leaf table 에 실제 데이터가 있게된다
    > heap table 이 없는 대신에 Clustered Index 의 실제 데이터 키 값을 들고 있게 된다, 이  키 값을 통해 cluster page 에서 데이터를 찾게 된다
    > Non clustered 에서 먼저 트리를 통해서 자식 중에서 cluster id에 를 찾고 이 id를 통해 cluster index 가 이루고 있는 트리 부모로 가서 여기서 다시 id 를 찾는 형태가 된다
    Non-Clustered  가 기존에 있을때 clustered index 를 추가 하면 기존에 Non-Clustered 도 영향을 주어 한단계 더 찾게 된다

 

 

기존 테이블을 지우고 테이블을 새로 하나 만든 다음
non clustered index 를 생성 하여 테스트

select *
into TestORderDetails 
from [Order Details];

select *
from TestORderDetails;


--non clustered index 를 생성
create index Index_OrderDetails
ON TestOrderDetails(orderID, ProductID);


--TestORderDetails 테이블에 있는 인덱스 정보 보기
exec sp_helpindex 'TestORderDetails';

 

TestORderDetails 테이블에 있는 인덱스 정보는 다음과 같다는걸 알 수 있다

 

 

인덱스 번호를 찾아 보려면 다음 처럼 작성하면 된다

sys.indexes 에서 오브젝트 id 를 비교하여 TestORderDetails 오브젝트 id 롸 같은 오브젝트의 index_id 들을 가져올 수 있다

select index_id, name
from sys.indexes
where object_id = object_id('TestORderDetails');

 

 

TestORderDetails 의 index_id 는 2 번이고 2 번으로 인덱스를 담고 있는 인덱스 페이지들을  볼 수 있다

--Index_OrderDetails Index_id 가 2 임으로 2를 인저로 넣어 페이지들을 보기
DBCC IND('Northwind', 'TestORderDetails', 2);

1048 이 root 고 나머지가 자식으로 들어간 걸 indexLevel 을 통해 알 수 있다

 

                              1048 

1008  1016  1017  1018  1019  1020

 

이 중에서 1008 페이지 정보를 보면 

--페이지 정보 보기
DBCC PAGE('Northwind', 1 , 1008, 3);

이 처럼 HEAP RID 가 있는 것을 알 수 있다 (index 를 non clustered index 로 생성했기 때문)

 

Heap RID = ([페이지 주소(4)[파일ID(2)[슬롯(2)] ROW)  의 바이트들로 구성 되는데이 ID 를 통해 Heap Table 테이블에서

Heap Table = [{page} {page} {page} {page}

 

몇번째 페이지 몇번 슬롯으로 되어 있는지를 Heap RID가 가리키고 있기 때문에

1008  에서 Heap RID 를 통해서 Heap Table 에 있는 데이터를 바로 검색해 찾을 수 있게 된다

 

 

DBCC  로 페이지 정보를 볼때 나오는 PageType 은 

  • 1 : Data Page
  • 2 : Index Page

를 나타낸다

 

 

 

 

현재 까지 상황은 

NonClusted Index 가 추가 된 상황이고  여기서  Clustered index 를 추가 해보면

-- clustered index 추가
create clustered index Index_OrderDetails_Clustered
ON TestOrderDetails(orderID);

이렇게 추가 된것을 볼 수 있다

 

이때 다시 2번 Non Clustered index 를 살펴보면 어떻게 달라져는지를 보면

 

cluster index 추가 하기 전
cluster index 추가 한후

 

 

페이지 번호가 바뀐 것을 알 수 있다

기존에는 

                              1048 

1008  1016  1017  1018  1019  1020

 

이런 번호였지만

1024 부터 자식이 시작 하는 걸 알 수 있는데 clustered index 를 추가 하게 되면 

 

                             1072

1024  1032  1033  1034  1035  1036

 

로 바뀌었다, 이때 1024 페이지 정보를 보면 

--페이지 정보 보기
DBCC PAGE('Northwind', 1 , 1024, 3);

원래는 Heap Rid 가 기존엔 있었지만 clustered index  를 추가하여 Heap Rid 가 없어진 것을 알 수 있다

 

그리고 페이지 id 가 전과 후가 달라진 것도 이전엔 Heap Rid로 heap table 을 찾는 형식이였지만

이젠 직접적으로 찾게 되니 PagePid 또한 달라지게 된 것

 

clustered index 를 생성할때 OrderID 에 대해 만들었는데

중복된 OrderID 를 볼 수 있는데 이것은 같은데이터에 한하여 동일한 ID 가 붙는 것이고 동일한 데이터를 구분하기 위하여

UNIQUIFIER 필드가 추가 되어 이들을 구분하게 끔 되어 있다

 

OrderID + UNIQUIFIER 조합으로 식별한다

 

 

정리하자면 Non clustered index 에서 Clustered index 를 추가 하면 기존 Heap RID 가 날라가게 된다는 것이다

 

 

 

 

위에서 본건 Non clusteered 인덱스 가 있었던건 본것이였는데

이번엔 그에 반해 위에서 새로 추가 했던 clustered 를 보면

--TestORderDetails 테이블에 있는 Index_id 보기
select index_id, name
from sys.indexes
where object_id = object_id('TestORderDetails');

 

테이블에 있는 인덱스 중에서 인덱스 1번은 검색

--Index_OrderDetails Index_id 가 2 임으로 2를 인저로 넣어 페이지들을 보기
DBCC IND('Northwind', 'TestORderDetails', 1);

 

 

                                                  9184

9144  9152  9153  9154  9155  9156  9157  9158  9159  9160

이렇게 구성 되어 있다 즉 여기서 알수 있는건 

 

DBCC  로 페이지 정보를 볼때 나오는 PageType 

  • 1 : Data Page
  • 2 : Index Page

였기 때문에 PageType 을 보면 PagePID 가 갖고 있는것이 바로 데이터 페이지가 된다는 걸 알수 있다

 

 

 

 

정리하자면

  • Clustered index 있으면 leaf  page 가 바로 데이터가 되는것이고, Heap Table 또한 없다
  • Clustered index 가 없다면 Heap RID로 Heap Table 을 검색해서 데이터를 찾는 한번 경유하는 형태가 된다는 것이다
    이때는 Heap Table 또한 생성되게 된다

 

 

반응형

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

[DB] 쓰기와 읽기, 스래드와 캐시 & 대기와 락, 트랜잭션  (0) 2023.04.03
[MSSQL] 예상 실행계획  (0) 2023.04.02
DB : 복합 인덱스  (0) 2023.03.02
DB : INDEX  (0) 2023.03.01
DB : 윈도우함수  (0) 2023.02.28
반응형

 

use Northwind;



SELECT *
FROM [Order Details]
ORDER BY OrderID;


select *
into TestORderDetails 
from [Order Details];


select *
from TestORderDetails;


--복합 인덱스 추가
create index Index_TestOrderDetails
ON TestOrderDetails(orderID, ProductID);


--인덱스 정보 보기
exec sp_helpindex 'TestORderDetails';


--Index Seek 이건 인덱스를 활용해 빨리 찾는 것인데
--Index Scan 은 Index Full Scan 으로 전체 다 찾는 거라 느리다
--아래 처럼 and 를 쓰면서 인덱스통하여 검색을 하면 Index Seek 가 일어난 다는 걸 알 수 있다
select *
from TestORderDetails
where OrderID  = 10248 and ProductID = 11;



--index seek 으로 검색
select *
from TestORderDetails
where OrderID  = 10248;



--table scan 으로 찾음
--이유는 ON TestOrderDetails(orderID, ProductID) 이렇게 생성했을대 orderID 가 첫번째에 있었기 때문에
--orderID 로 찾기를 시작할땐 index seek 로 1차적으로 찾는데 
--그렇지 않으면 ProductID 로 찾게 되면 2차적으로 정렬하여 table scan 으로 찾게 된다
select *
from TestORderDetails
where ProductID  = 10248;


--index 정보 보기
DBCC IND('Northwind', 'TestORderDetails', 2);

DBCC PAGE('Northwind', 1 , 9160, 3);

 

 

 


복합 인덱스를 두개에대해 추가(orderID, ProductID) 했다
create index Index_TestOrderDetails
ON TestOrderDetails(orderID, ProductID);

 

이렇게 하면 데이터를 찾을때 성능에 대한 차이가 발생 하는 이유는 데이터를 정렬 하는데 있어서 차이가 있어서 그런것인데 인덱스가 두개라서 어떻게 검색에 대한 처리를 하는지 보면 

 

 

 

--index 정보 보기
DBCC IND('Northwind', 'TestORderDetails', 2);

9200이 가장 상단이고 나머지는 모두 자식 즉 Leaf 노드 인 것을 알 수 있다(IndexLevel 이 0 임으로)

 

Leaf 노드의 순서가 트리상 동일한 레벨에서 9168 -> 9169 -> 9170->  이런 순으로 나가기 때문에

9160에 대한 노드 정보를 보면

 

OrderID 와  ProductID 두개를 보면 우선 OrderID 로 정렬을 하는데 만약 같은 ID 라면 그다음 ProductID로 비교하는 걸 알 수있다 

 

OrderID 는 정렬이 되어 있는 반면 ProductID 는 첫번째 것을 기준으로 정렬 되어 있어서

바로 ProductID로 찾으려 하면 정렬이 안되어 있는것과 마찬가지 임으로

ProductID 로 검색 기준을 삼으면 Scan 이 되는 것 => 느림

 

 

인덱스(A, B) 를 글었다면 인덱스 A 에 대해 따로 인덱스를 걸어줄 필요는 없는데

B로도 검색이 필요하다면 인덱스(B) 에 대해 별도로 걸어줘야 빨라지게 된다

주의 : A,B 둘다 인덱스가 독립적으로 걸려있는게 아니다

 

 

 


페이지 분할(SPLIT) : 정리하면 인덱스를 생성하면 원소들이 페이지 단위로 관리 되는데 이 페이지 단위마다 관리 개수를 넘어가면 추가 페이지를 만들어 원소들을 관리하는 구조가 된다

 

데이터 50개 강제로 추가 해보면
인덱스 데이터 추가 /갱신/ 삭제시 그 중에서 추가 하는 경우를 보자

DECLARE @i INT = 0;
WHILE @i < 50
BEGIN
INSERT INTO TestORderDetails
VALUES (10248, 100 + @i, 10, 1, 0);
SET @i = @i +1;
END


이렇게 하면 추가 페이지가 만들어지는데 그 이유는 한 페이지에 내용이 너무 많아 지면 기존 페이지에 있던 요소 를 두개,세개 로 분할하여 담기 때문이 새로 페이지가 추가된것을 알 수 있고 이것을 페이지 Split 이라 한다

 

 

주의 : 인덱스를 Name 에 대해 걸어 놓았을때 SUBSTRING(NAME,1 ,2) 을 하게 되면 인덱스를 사용하는 최적화 사용이 불가능함으로 Index Scan 을 하게 되는 경우가 발생할 수 있다

SUBSTRING 으로 문자를 일부 가져와 어떤 문자를 찾는 것이 아닌

WHERE Name LIKE 'Ba%' ; 식으로 찾으면 Name 에 대해 최적화를 적용 받을 수 있다

 

실행 할때 Ctrl + L 로 Index Seek 인디 Index Scan 인지 확인하는 것이 좋다

 

 

 

반응형
반응형

테이블과 데이터들을 추가한다

USE Northwind;

--db 정보 보기
EXEC sp_helpdb 'Northwind';

create table Test
(
	EmployeeID INT NOT NULL,
	LastName NVARCHAR(20) NULL,
	FirstName NVARCHAR(20) NULL,
	HireDate DATETIME NULL,

);

SELECT *
FROM Test;

--Employees 데이터를 Test 에 추가한다
INSERT INTO Test
SELECT EmployeeID, LastName, FirstName, HireDate
FROM Employees;

RESULT

 

 

 

 

--index 를 걸 컬럼= LastName
--FILLFACTOR 리프 페이지 공간 1%만 사용 , 전체 사용 공간 중에 1%만 사용 하겠다는 것, 이러면 데이터가 다 안들어가기 때문에 별도 트리구조로 데이터를 저장함
--PAD_INDEX (FILFACTOR 가 중간 페이지에도 적용되게 하는 것) => 결과적으로 공간을 비효율적으로 사용하게끔 하는 것인데 목적이 테스트 하는 것임
CREATE INDEX Test_Index ON Test(LastName)
WITH (FILLFACTOR = 1, PAD_INDEX = ON)


--인덱스가 만들어진 sys.indexes 에서 index_id와 name 을 출력하는데 조건이 
--Test 에 만든 인덱스 id 와 같은것만 출력한다
SELECT index_id, name
FROM sys.indexes
WHERE object_id = object_id('Test');

 

결과 화면은 이런데 여기서 인덱스 Test_Index 를 조사해보면

 

--2번 인덱스 정보 살펴보기
DBCC IND('Northwind','Test', 2);

빨간색 박스의 IndexLevel 이

 

가장 높은 숫자가 트리의 Root 이고

2 -> 1 -> 0 

순으로 자식을 이루고 있는 인덱스 트리를 생각 하면 된다

 

2 가 가장 상단 그다음에 1에 해당 하는 것이 PagePID 보면 1008, 984 두개가 있는데

이 중에서 NextPagePID 를 1 레벨에서 다음을 말하는 것이 984 라고 나와 있는 것

즉 순서가 985 -> 1008 -> 984  로 되어 있다는 것을 알 수 있다

 

 트리 형태로 보면 가장 상단 레벨 1에 1개 2 레벨에 2개 3 레벨에 3개의 노드가 있다고 생각하면 된다

그런데 각 노드는 페이지들인데 이 정보들 또한 볼 수 있다

 

끝단 3개의 노드를 출력해보면

--1 파일번호, 968 페이지 번호, 3 출력 옵션
DBCC PAGE('Northwind',1, 968, 3);
DBCC PAGE('Northwind',1, 976, 3);
DBCC PAGE('Northwind',1, 977, 3);

 

 

위 처럼 되고 

원래 추가된 9개 와 비교해보면 3개씩 나뉘어져 들어가 있는 것을 알 수 있다

 

HEAP RID 라는 걸 볼 수 있는데

HEAP RID = 페이지주소(4바이트) , 파일 ID(2), 슬롯번호(2), 로 조합된 8바이트 ROW 식별자이고 이걸로

테이블에서 정보를 추출한다

 

TABLE [{PAGE} {PAGE} {PAGE} ....]  여기서 찾는 형태이다

 

 

RANDOM ACCESS (한건을 읽기 위해 한 페이지씩 접근 하는것 으로 트리 위에서 부터 아래로 노드를 찾아가는 과정)

BOOKMARK LOOKUP : 노드를 찾은다음 RID를 통해 행을 실제 TABLE 에서 찾는 것을 말함 

반응형
반응형

함수명을 쓰고 OVER 쓴다음 범위를 지정해주면 된다

OVER( [PARTITION] [ORDER BY] [ROWS] ) : 이걸로 서브집합을 선택해주는 것

 

ROW_NUMBER : 맨 왼쪽 인덱스 번호

RANK : 순위인데 중복되면 중복된 것 만큼 순위는 건너 띔

DENSE_RANK : 만약 1등이 두명이 나오면 그다음 3등이 2등이 되는 RANK

NTILE : 백분율로 가장 상위가 1% 로 1 로 나옴 , 가장 하위가 100

 

 

 

파티션의 경우 playerID로 Group by 와 유사하게 할수 있는데 다른 점은 Group by 는 하나로 묶어 버리지만 Partition 의 경우에는 각 행별로 모두 보이게 된다

결과

 

PlayerID 별로 랭킹이 매겨져 있는 걸 볼 수가 있다

 

 

 

LAG(바로 이전 : 나열된것의 바로 위) ,LEAD(바로 다음 : 나열된 것을 바로 아래)

 

 

 

결과

 

playerID 기준으로 salary 값 위아래를 보면 prevSalary 와 nextSalary 값이 있다는 것을 알 수 있다

 

 

FIRST_VALUE : 가장 처음 값 : 여금 여기선 큰 

LAST_VALUE : 가장 마지막 값 : 지금 여기선 작은 값

 

 

결과를 보면 WORST 가 제대로된 작은 값이 아닌데 

그 이유는 Desc 로 정렬하여 큰 값은 나오게 되는데 나오는 검색 과정이

동일 아이디의 가장 위에서 부터 하나씩 증가 시키면서 그 중에 큰걸 보게 되는 순서라

4500000 

4500000 , 2750000

4500000 , 2750000, 500000

이런순을 검색하면서 그 중 가장 큰걸 뽑아오는게 FIRST_VALUE 라서 best 는 제대로 되지만 worst 는 제대로 되지 않는다는걸 알 수 있다

 

 

 

 

 

 

그래서 LAST_VALUE 의 경우에는 제일 처음부터 증가하는 형태인 키워드 (UNBOUNDED PRECEDING) 를 넣어주고 현재 까지의 진행라인에 대한  현재 CURRENT_ROW 까지 는 BEST 라인이 범위가 되도록 ROWS 를 지정해주면 되고 

 

그다음 LAST_VALUE 에대해선 현재 라인 CURRENT_ROW 부터 동일 아이디에 대해서 마지막 라인을 얘기하는 UNBOUNDED FOLLOWING 을 넣어주면제대로된 WORST 값이 나오게 된다

 

제대로된 결과

 

 

 

오라클에서 OVER 절에 WINDOWING 절을 처음 사용할 때 ROWS와 RANGE가 어떤 차이점이 있는지 많이 헷갈릴 수 있다. 간단히 설명하면 ROWS는 각 행의 위치고, RANGE는 값의 범위라고 생각하면 된다. 아래의 예제를 여러 번 반복해서 보면 많이 어렵지 않게 이해할 수 있을 것이다.

 

아래의 키워드 의미부터 숙지를 하고 예제를 보면 더욱 이해하기 쉬울 것이다.

키워드 설명
 ROWS  물리적인 행 단위
 RANGE  논리적인 행 집합
 CURRENT ROW  현재 행
 UNBOUNDED PRECEDING  첫 번째 행
 UNBOUNDED FOLLOWING  마지막 행
 [위치] PRECEDING  [위치] 이전 행
 [위치] FOLLOWING  [위치] 다음 행

 

 

REF : https://gent.tistory.com/473

반응형

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

DB : 복합 인덱스  (0) 2023.03.02
DB : INDEX  (0) 2023.03.01
Northwind and pubs sample databases for Microsoft SQL Server  (0) 2023.02.23
DB : 테이블을 변수에, IF 문, WHILE 문, BREAK , CONTINUE  (0) 2023.02.20
DB : BATCH : GO  (0) 2023.02.19
반응형

 

 

게임, 전화, 디스플레이, 네트워크 데이터 등등 각각의 프로세스별로 레디큐를 두어 처리 하는 방식이다

(게임 ,전화 등등이 우선순위가 하나의 기준으로 우선순위가 처리되지 않음으로)

각 우선순위를 위한 분리된 큐

 

 

 

위에 있는 것일 수록 우선순위가 높음

각각 레디큐가 별도고 가장 높은 우선순위의 큐가 비게 되면 그다음 system process 의 큐를 실행하고

system process 도 다 끝나면 그다음 interactive process (예를 들어 ui같은) 를 그다음 순위로 처리하는 방식이다

 

그런데 이때 문제점이 있는데

가장 상단의 우선순위가 높기 때문에 아래 있는 것일 수록 실행 되지 않는 기아상태가 발생할 수도 있기 때문에

 

그래서 우선순위가 가장 높은 큐는 퀀텀을 8 을 줘서 짧게 실행하게 하고 나가고 그다음 우순위에선 퀀텀 시간을 16을 줘서 좀 더 실행하게 하고 그다음 후순위는 FCFS 로 아래로 갈 수록 더 많은 CPU Burst time 을 할당 받기 때문에 아래 있는 것도 우선순위가 낮아 대기는 하지만 한번 실행될때 대기 한 만큼 좀 더 실행이 되도록 하여 어느정도 균형을 좀 맞출수 있는 형태가 된다

 

 

 

그런데 현대적 컴퓨터에선 프로세스 스케줄링을 하지 않고 스레드 스케줄링을 한다

그런데 지금까지 본것은 프로세스 스케줄링인데 스레드 스케줄링은 프로세스 스케줄링에 견주어 생각해보면 된다

그리고 프로세스에는 커널스레드와 유저스레드가 있는데 이 중에서도 커널 스레드만 스케줄링 하면 되는데

유저 스레드는 스레드 라이브러리에 의해서 관리 되는 소프트웨어적인 측면이기 때문에 커널 유저스레드에 대해 모른다

즉 커널 스레드가 스케룰이 되면 커널스레드는 유저스레드를 서비스만 해주면 된다

 

즉 지금까지 본것은 커널스레드를 스케줄링한다고 생각하면 된다

 

 

 

반응형
반응형

기본적으로 SJF 인데 비교 대상이 Burst Time 이 아니가 Priority 를 두어 이것을 기반으로

먼저 처리 할것을 선정하는 방식이다

 

이때 발생하는 문제가 실행시간이 짧은 프로세스가 있는데 우선순위가 먼저 처리되야 하는게 계속 들어오게 되면 실행되지 못하고 게속 대기해야 하는 기아(starvation) 이 발생하게 되는 문제가 있다

 

 

 

그래서 이 문제를 해결 하기 위해서 하는 방법은 프로세스에 aging 숫자를 도입해 오래 된 프로세스일 수록 우선순위를 높여주는 처리를 하여 이 문제를 막을 수 있다

 

 

 

 

좀더 나은 대안으로 RR 과 우선순위 기반의 스케줄링 두개를 섞어서 사용하는 방식도 있다

즉 우선순위를 먼저 처리하고 RR 로 돌아가면서 실행되도록

 

먼저 우선순위 높은걸 처리한다음 동일한 우선순위의 프로세스에 대해선 RR 로 처리한다

 

처음 P4 우선순위가 높기 때문에 P4 가 7초 동안 실행된 다음 P2, P3 우선순위가 같기 때문에 time quantum 즉 시간을 분할 하여 2초 마다 P2, 와 P3  환원큐 방식으로 돌아가면서 처리 되도록 하는 형태인걸 알수 있다

 

 

반응형
반응형

 

선행글 : https://3dmpengines.tistory.com/2340

 

이렇게 남아 있는 시간중에 더 짧은 시간인것이 먼저 처리 되게 하는 방식을

SRTF : 즉 Preemptive SJF 스케줄링 이라 한다 , R 은 Remaining

 

0초에 P1 이 도착

1 초에 P2 가 도착

2 초에 P3 가 도착

3 초에 P4 가 도착

 

위 처럼 뭉처 있게 되는데

 

실행순서를 보면 처음 P1 은 레디큐에 아무것도 없기 때문에 바로 실행된다

 

이때 1초가 지났을때 즉 절대 시간 1초에서 보면 

 

P1 의 Burst Time 은 8초에서 이제 7초가 남았고 

P2 가 이제 들어오게 되는데 P2 의 Burst Time 이 4 라서 P1 에 남아 있는 시간

7초보다 작으니 P2가 선점되어 실행되게 된다

 

그다음 절대 시간 3초에서 보면 P2의 남은 시간은 3초고 P3가 이제 도착 했는데 Burst Time 이 9초 임으로

P2 가 계속 실행된다 

 

절대시간 4초에서 보면 P2의 남아 있는 시간은 2초고 P4의 Burst Time 이 5초 임으로 마찬가지로 P2 가 계속 실행된다

 

절대시간 5초에서 보면 P2 는 끝났고 이제 남아 있는 P1의 시간과 나머지 P2, P4 와 마찬가지로 시간 경합을 하여 실행하게 된다

 

최종적으로 실행된 결과는 다음 처럼 된다

사실 위에 있는 이미지와 동일한데 한번더 쓴것

 

 

 

이것은 SJF 하고 비교하면 SRTF 가 평균대기 시간이 좀더 짧을 수 있다

실제 계산해보면 SJF 는 평균 대기 시간이 7.75가 된다

 

 

반응형
반응형

SRTF의 경우 짧은 시간을 먼저 실행하도록 계속 변경하다보면

만약  긴 CPU Burst time 동안 실행 되는게 있을때 계속 바뀌면 유저가 끊기는 불편을 느낄 수도 있다

게임이 끊겨서 버벅 거리는 것 과 같이 

 

 

 

RR 은 FCFS 를 돌면서 처리 하는 것인데 

FCFS 에서 시간을 쪼개서( time Quantum 또는time slice )실행하게 한다음 빠져나가게 처리 하는 방식 

그런데 이 쪼개는 시간 단위를 아주 작은 시간단위로 준다 10 ~ 100 밀리 세컨드의 길이로 분할한다

 

이렇게 되면 Ready Queue 에서는 환원큐로 구현하고

 

P1, P2, P3, P4 그다음 다시 P1 앞의 것이 실행 되도록 시간을 분할하여 돌아가며 실행되는 형태이다

 

 

 

그런데 만약 time quantum 시간 보다 프로세스가 처리 되는 시간이 짧다면?

레디큐에서 다음 실행할 프로세스를 바로 실행시키고

1 time quantum 시간 보다 길다면 OS 에서 시간이 됐을때 인터럽트를 발생시켜서 컨텍스트스위치가 일어나게 되고 다음 프로세스 실행시킨다

RR 은 평균 대기 시간이 조금 더 길수는 있다

RR은 time quantum 을 얼마는 주는지에 따라 성능이 확확 달라진다

quantum 시간을 짧게 주면 줄수록 즉 잘게 짜를 수록 컨텍스트 스위칭이 자주 발생하게 되어

전환시간 또한 높아지게 된다(dispatch latency)

quantum 을 최대로 길게하면 FCFS 가 된다

 

 

 

반응형

'운영체제 & 병렬처리 > 시스템프로그래밍' 카테고리의 다른 글

스케줄링 시간 : Priority-Base 과 RR  (0) 2023.02.26
스케줄링 시간 : SRTF  (0) 2023.02.25
스케줄링 시간 : SJF  (0) 2023.02.23
스케줄링 시간 : FCFS  (0) 2023.02.22
스케줄링  (0) 2023.02.21
반응형

SJF(Shortest-Job-First)

수행시간이 적은 것을 먼저 처리 하는 방식

만약 다음 처리 시간이 같다면 FCFS 로처리

 

 

 

Process 가 Ready Queue 에 있고 Burst Time 이 주어질때

 

Burst Time 이 가장 작은것 먼저 실행 시키면 위 처럼 간트차트가 된다

 

총 대기시간 28초

평균대기시간은 7초

 

턴어라운드 타임은 프로세스당 앞에 기다린 시간과 해당 프로세스가 처리 되는 시간까지 임으로

 

토탈 턴어라운드 타임은 52

평균 턴어라운드 타임은 13이 된다

 

 

최적이 증명가능하다

평균 대기 시간이 왜 최소로 갖느냐면 짧은게 앞에 있으니 대기시간이 짤방질 수 밖에 없다

 

그런데 이건 구현이 불가능한데, 다음 CPU Burst 타임을 알수가 없기 때문이다

대신 근사적으로 구하는건 예측을 통해서 구현할수 있는데 

예측은 이 프로세스가 과거에 얼마만큼 시간을 썼는지를 보고 판단하게 된다

 

시간은 지수적 시간을 적용하는데 그 이유는 최근에 많이 썼을 수록 가중치를 더 줘서 CPU burst 시간을 조정한다

 

10*1/2 + 6*1/2 = 8

타우식은 CPU Burst 타임이 타우 그래프에 따라 파란색 처럼 이동 평균선 역할을 해준다

 

그런데 이 전 사용시간에 대해서도 저장처리를 해야 하기 때문에 이론적으론 옵티멀한데 실제 사용하긴 어렵다

 

 

SJF 는 Preemtpvie 할 수도 있고 Non-Preemptive 할 수도 있다

앞서서 5초로 CPU burst 이 계산된게 있는데 새로 계산된 프로세스의 시간이 1 이라면 

이때 1초가 먼저 실행 되면 선점형이고 기다렸다 처리되면 비선점형이 된다

그런데 이렇게 할려면 해당  프로세스의 남은 CUP burst Time 을 알아야 하기 때문에 이것 또한 쉽지 않다

 

 

 

 

 

반응형
반응형

Northwind  Database 인데 오리지널 버전은 SQL Server 2000 으로 만들어졌었지만 

최근 MS Server 에서도 파일을 집적 복붙하여 사용가능하다

instnwnd.sql 파일

 

생성하고 나면 웹사이트 같은 느낌이라 테스트해보기에 좋다 

고개 정보, 주문, 제품, 지역, 직원 등등의 정보들이 있다

 

Northwind and pubs sample databases for Microsoft SQL Server

This folder contains scripts to create and load the Northwind (instnwnd.sql) and pubs (instpubs.sql) sample databases.

These scripts were originally created for SQL Server 2000.

 

 

Before you begin

To run this sample, you need a tool that can run Transact-SQL scripts. You can run the scripts in the following tools:

Run the scripts in SSMS

 

 

https://github.com/microsoft/sql-server-samples/tree/master/samples/databases/northwind-pubs

 

GitHub - microsoft/sql-server-samples: Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for

Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge - GitHub - microsoft/sql-server-samples: Azure Dat...

github.com

https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/databases/northwind-pubs/instnwnd.sql

반응형

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

DB : INDEX  (0) 2023.03.01
DB : 윈도우함수  (0) 2023.02.28
DB : 테이블을 변수에, IF 문, WHILE 문, BREAK , CONTINUE  (0) 2023.02.20
DB : BATCH : GO  (0) 2023.02.19
DB : 변수  (0) 2023.02.18
반응형

 

선행글 : https://3dmpengines.tistory.com/2338

 

 

 

시간 0 에서 위 프로세스들이 도착 했다고 가정 했을때 CPU 사용 시간이 24, 3, 3 이라 가정했을때

FCFS 는 다음과 같을 것이다 (아래 그림을 간츠차트라 한다)

FCFS : non-preemptive 로 비선점형이다

 

도착은 P1, P2, P3 순으로 도착하게 되고 P1 은 24 초에 끝나고 P2는 27초에 끝나게 된다

 

대기 시간을 보면

 

P1은 대기 시간이 0 

P2는 24

P3는 27이 된다

 

그래서 총 대기 시간은 0 + 24 + 27 = 51 초가 된다

평균은 51/3 = 17초가 된다

 

이때 턴어라운드 타임은 = p1=24, p2=27, p3=30  이 되고 

총 턴어라운드 타입은 = 24+27+30 = 81

평균 턴어라운드 타임은 = 81/3 = 27 이 된다

 

 

 

이때 평균 대기 시간을 줄일수 있는가? 에 대한 질문이 나오게 된다

여기서 선수를 바꾸면

이렇게 되고

 

그래서 총 대기 시간은 9 초가 된다

평균은 3 초가 되어 줄어들게 된다

 

이때 턴어라운드 타임은 = p1=3, p2=6, p3=30  이 되고

총 턴어라운드 타입은 = 3+6+30 = 39

평균 턴어라운드 타임은 = 39/3 = 13 이 된다

 

결론 : FCFS 같은 경우 프로세스의 처리 시간과 순서에 따라 평균 대기 시간이 확달라진다

FCFS 로는 좋은 효율을 얻기에는 어렵다

 

 

반응형

+ Recent posts