NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# C# .NET Core 매크로 프로그램 만들기. (변수 1부)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 매크로의 꽃! 매크로의 핵심이라고 할 수 있는 변수 기능을 만들어 보겠습니다. 일단 변수가 뭔지 알아야 하는데요. 이 부분은 엔지엠 매크로를 사용하시는 분들이라면 다 알고 있는 내용이라서 설명하지 않고 넘어가도록 하겠습니다. 혹시라도 변수가 뭔지 잘 모르는 분들은 아래 글을 참고 해보세요.

    [ 변수란? ]

     

    엔지엠 매크로에서 변수도 프로그래밍 언어에서의 변수와 의미는 같습니다. 다만, 각각의 액션들의 데이터를 주고 받기 위한 저장 공간으로 사용한다는 것만 기억하면 될듯 합니다. 예를 들어서 액션 1에 숫자 10이 저장되어 있고, 액션 2에 숫자 100이 저장되어 있을 때 이 두 값을 연산하려면 액션 3에서 액션 1의 값과 액션 2의 값을 가져와야 사칙연산이 가능합니다. 지금은 파편적으로 다 떨어져 있어서 액션 3에서 연산할 수 없습니다. 그래서, 임시 저장 공간인 변수에 10과 100을 저장해두고, 액션 3이 각각의 변수에 값들을 빼와서 연산하는 방식입니다.

     

    변수를 글로 이해하기란 쉽지 않습니다. 그래서, 스크립트를 제작해보면서 변수를 다뤄봐야 비로소 이해가 될겁니다. 물론, 개발자분들이야 이해하는데 문제가 없겠지만요. 변수를 정의하는 모델을 만들어볼께요. 프로그래밍에서는 아래와 같이 단순하게 변수를 선언하고, 값을 할당할 수 있습니다.

    var name;
    name = "홍길동";

     

    변수 모델을 하나 만들고, 아래와 같이 코딩했습니다.

    public class AddVariableModel : ActionModel

     

    프로그래밍에서 name은 변수명입니다. 그리고, name 변수에 저장된 값은 홍길동인데요. 이렇게 저장 공간을 만들고 값을 가져오거나 넣으려면 어느 공간인지 이름을 알아야 합니다. 그래서 KeyValuePair 형식을 사용해야 합니다. Data.VariableItem은 KeyValuePair를 구현하는 클래스입니다.

            /// <summary>
            /// 변수를 가져올 변수 목록을 가져오거나 설정합니다.
            /// </summary>
            [LocalizedCategory("Variables")]
            [LocalizedDisplayName("Get")]
            [LocalizedDescription("Get")]
            [Browsable(true)]
            [Editor(typeof(TypeEditor.VariableEditor), typeof(UITypeEditor))]
            public List<Data.VariableItem> GetVariables { get; set; } = new List<Data.VariableItem>();
    
            /// <summary>
            /// 변수를 추가할 변수 목록을 가져오거나 설정합니다.
            /// </summary>
            [LocalizedCategory("Variables")]
            [LocalizedDisplayName("Set")]
            [LocalizedDescription("Set")]
            [Browsable(true)]
            [Editor(typeof(TypeEditor.VariableEditor), typeof(UITypeEditor))]
            public List<Data.VariableItem> SetVariables { get; set; } = new List<Data.VariableItem>();

     

    VariableItem 클래스는 아래와 같습니다.

    public class VariableItem : BaseItem, IComparable

     

    변수명(Key)과 값(Value)는 Name과 Data입니다. 단순한 속성으로 만들어도 되지만, GUI를 가지는 클라이언트와 데이터를 공유하기 위해 코드가 복잡해졌습니다. 전체적인 시나리오는 변수명을 설정하고, 데이터를 추가하거나 가져오기 위한 Dialog를 표시합니다. 이 안에 변수들을 목록으로 표시합니다. 이름은 문자열로 처리가 가능하지만, 변수에 저장하는 데이터는 object 형식입니다. object는 어떤 형식으로 들어올지 알 수 없기 때문에 적절하게 캐스팅 처리를 해줘야 합니다.

            [Browsable(true)]
            [LocalizedDisplayName("VariableID")]
            [DefaultValue(null)]
            [TypeConverter(typeof(TypeConverter.VariableConverter))]
            public required string Name { get; set; }
    
            [Browsable(true)]
            [LocalizedDisplayName("VariableData")]
            [DefaultValue(null)]
            [TypeConverter(typeof(TypeConverter.VariableConverter))]
            public object? Data
            {
                get { return _displayProperty; }
                set
                {
                    if (value == null)
                        return;
    
                    _displayProperty = value.ToString();
                    var item = value as VariableItem;
    
                    if (item != null)
                    {
                        _displayProperty = item?.Name?.ToString();
                        PropertyName = item?.Data?.ToString();
                    }
                }
            }

     

    기존의 엔지엠 6에서는 변수의 타입을 사용자가 설정해야 했습니다. 사실 개발자가 아닌 경우 String, Integer, Double, Float, Decimal, DateTime, DataTable... 과 같은 데이터 형식이 뭔지 알기가 어렵습니다. 이렇게 강력한 형식(Strong Type) 지정을 사용하는건 근데 프로그래밍 언어의 트렌드입니다. 엔지엠 매크로도 트렌드에 맞게 설계되었으나 이 부분에 문제가 많았습니다.

     

    개발자에게 트렌드지 일반인들이 사용하는 업무 자동화 솔루션에서는 의미가 없었습니다. 그래서 차세대 매크로 프로그램은 데이터를 분석해서 자동으로 타입을 유추하고, 캐스팅 해주도록 설계하고 디자인했습니다. 인공지능 매크로(AI Macro)라는 이름에 맞게 사용자가 입력하는 내용을 분석하고 알아서 처리해주는게 좋겠죠?

     

    하나의 스크립트에서뿐만 아니라 클라이언트 레벨에서 데이터를 교환하기 위해 아래와 같이 옵션을 2개 추가했습니다.

            [Browsable(true)]
            [LocalizedDisplayName("IsGlobal")]
            [DefaultValue(false)]
            public bool IsGlobal { get; set; }
    
            [Browsable(true)]
            [LocalizedDisplayName("IsParent")]
            [DefaultValue(false)]
            public bool IsParent { get; set; }

     

    이제 클라이언트와 변수에 저장되는 아이템간에 통신할 수 있는 중간 매개체를 만들어 봅시다. 변수는 여러개를 등록할 수 있기 때문에 CollectionEditor를 상속 받아서 변수 목록 편집기를 만들어줍니다. 이 안에서 스크립트내에 있는 모든 변수들을 목록화하여 보여주고, 사용자가 선택할 수 있도록 합니다.

    using System.ComponentModel.Design;
    
    namespace Ai.Model.TypeEditor
    {
        public class VariableEditor : CollectionEditor
        {
            public VariableEditor(Type type) : base(type)
            {
            }
    
            protected override CollectionForm CreateCollectionForm()
            {
                CollectionForm form = base.CreateCollectionForm();
                form.Tag = base.Context;
                return form;
            }
        }
    }

     

    위 코드를 액션의 어트리뷰트로 등록하면, 아래와 같이 VariableItem 컬렉션 편집기가 만들어집니다. 편집기의 콤보박스에 VariableItem이 목록으로 표시됩니다. 클라이언트에서 변수 관련 옵션을 선택하면 컬렉션 편집기가 표시되어 변수 아이템과 연결해주는 중간 매개체 역할을 할 수 있습니다.

    YKVoIvE.png

     

     

    변수의 경우 여러개의 스레드(스크립트 또는 플레이어)에서 접근할 수 있습니다. 그리고, 계속해서 데이터를 추가하거나 삭제합니다. 물론, 업데이트도 동시에 이루어질겁니다. 그래서, 이전 버전에서는 비싼 비용을 치르는 루틴중에 하나였습니다. 새로운 버전에서는 이런 문제를 해소하기 위해 Thread-safe 컬렉션을 사용합니다. 그리고, 로직적으로 처리하던 것을 TryAdd와 AddOrUpdate로 단순화 했습니다. 아래는 AddVariableModel 클래스의 핵심 코드입니다.

            public override void Excute(IPlayer player)
            {
                if (string.IsNullOrEmpty(ID))
                    return;
    
                ConcurrentDictionary<string, object>? variables = null;
    
                if (IsGlobal)
                    variables = player.Manager.Variables;
                else
                {
                    if (player.HasParent && IsParent)
                        variables = player.Parent?.Variables;
                    else
                        variables = player.Variables;
                }
    
                if (IsConstant)
                    variables?.TryAdd(ID, DefaultValue ?? string.Empty);
                else
                    variables?.AddOrUpdate(ID, DefaultValue ?? string.Empty, (key, value) => DefaultValue ?? string.Empty);
            }

     

    여기까지 변수를 생성하고, 등록하는것까지 알아봤습니다. 아직 처리해야 할 내용들이 더 많이 남아 있는데요. 변수를 액션들이 공유하고, 어떻게 데이터를 가공하는지는 변수 2부에서 자세하게 알아보도록 하겠습니다. 사실, 이 부분이 더 까다로운 부분입니다. 프로그래밍 언어에는 수많은 형식이 존재합니다. 사실, 거의 무한에 가깝다고 해도 과언이 아닌데요. 이런 형식들을 어떻게 가공해서 재사용할 수 있는지를 고민해야 합니다. 다음 내용도 기대 해주세요^^

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.