perpet

 자신들이 만든 게임의 캐릭터 액션정보나 AI 스테이트 정보를 비주얼툴로 보거나 보여주고 싶을때가 있다.

간단한게 외부라이브러리를 이용해 하루만에 뷰어를 만들수있는 툴을 만들어보자.

 

작업프로세스는 이렇다.

게임 액션과 액션사이에 링크정보가 있다 이것은 특정 이벤트가 발생해야만 다른액션으로 이동을 하는 구조를 가질수있다.

가령 stand 액션에서 key_left 가 눌러지면 left_uun , key_right ,눌리면 right_run 이란 액션으로 넘어간다고 하고 이것을 텍스트 형식으로 저장을 하면

state : stand

link : key_left, left_run

link : key_right , right_run

state : left_run

state : right_run

 

이런식으로 기획자들이 자신들이 편한 방식으로 텍스트, 또는 xml 로 저장을 할것이다.

프로그래머가 자주 이 링크정보를 보고싶을때가 있는데 이정보가 일반적으로 한캐릭에 수십가지의 액션정보가 있어 텍스트로 보기로는 상당히 힘들다

 

 이런텍스트를 uml 툴같은 다이어그램으로 보고 싶을때가있는데 여기서 가장 손쉽고 빠르게 만드는 방법을 설명한다.

 

작업방식은 공개된 다이어그램 뷰어를 구해서 그포맷에 맞게 자신들의 콘텐츠정보를 컨버트 해주면된다.

여기서는 http://www.graphviz.org/ 를 사용했다. 우리가 해줄것은 이 공개소프트웨어가 인식가능한 포맷으로 나의 컨텐츠 정보를 읽어와 변경해주면된다.

 graphviz    에서 dot 포맷이 있는데 이것으로 직접 내가 포맷을 만들어줘도 되지만 이 컨버팅 작업또한 공개소스를 이용하면 간단하게 해결할수있다.

http://quickgraph.codeplex.com/ 에가면 쉽고 간단하게 dot 포맷으로 변경가능하다.

 

전체 작업방식은 내콘텐츠 정보를 읽어 QuickGraph  를 이용해 dot 포맷파일을 생성하고  graphviz 로 이파일을 가지고 원하는 다이어그램 이미지를 만들면된다.

 

 QuickGraph 로 dot 를 생성하면

 digraph G {
0 [label="stand"];
1 [label="left_run"];
2 [label="right_run"];
0 -> 1 [ label="key_left"];
0 -> 2 [ label="key_right"];
}

 

이 만들어지고

 

  graphviz  를 돌리면

 

command>  dot a.dot -Tjpg -o a.jpg

 

 

Throwing_Spirit_Grudge.gif 

 

 

 파일이 나온다.

 

 

 

 

 

 

 

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

소스요청하시는분들이 몇분계서서 올려요

설명은 http://blog.daum.net/hazzling/16781469
참고하시면됩니다.

1년동안 다른거 공부하랴 c# 게임서버에 신경을 못쓰고 있네요.

간단하게 제가 제시하는 코딩방식은 이렇습니다.

 

예를 들어 설명하자면 유저가 로그인하여 아이디와 패스워드를 검증하는 코딩을 짠다고 봅시다.

보통 디비쿼리시 디비에 쿼리를 날리는순간 블럭이 되여 이것을 비동기 처리합니다.

그래서 코드가 보통이렇게 되죠.

 

void login_packet(string id, string pass)

{

int x;

db.query_login(id,pass,query_resultLogin) ; // 쿼리 결과 핸들 등록

return;

}

 

void query_resultLogin(int ret)

{

   if( ret == 0)   // 결과에 따라 처리

 

   client.send_login_result(ret) ;

 

}

 

이걸 c# 에서 yield 를 이용한 코딩을 하면 간단하게

 

void login_packet(string id, string pass)

{

int x;

yield db.query_login(id,pass) ;

 

  if( ret == 0)   // 결과에 따라 처리

 

   client.send_login_result(ret) ;

}

 

이렇게 표현될수있다는겁니다.

이런구조의 장점을 들어보자면

  1.  함수하나의 스코프안에서 원하는 구현부위를 표현하여 직관적입니다.
  2. 원시적인 비동기콜방식으로 할경우 쿼리 전 변수가 쿼리결가후에도 필요하면 해당변수를 외부에 저장할 글로벌 변수로 만들어야합니다. 위에 x 라는 변수가 쿼리 결과에서도 참조가 필요한 케이스가 발생할경우..( yield 를 하면 필요가 없겠죠)
  3.  명시적인 리모트 함수선언을 할수있습니다. 지금까지는 일반적인 패킷함수를 표현하면 void login(id,pass)  를 표현하면 알아서 loing 패킷을 만들어주고 send 함수를 만들어주는 수준이지만 yield  구조를 갖으면 리턴받아야하는 패킷도 명시적으로 표현할수있습니다.  가령 int query_login(id,pass) 라고 패킷을 선언하면 컨텐츠코딩단에서 해당하는 query_login 결과값이 올때까지 다른핸들링이 작동을 안하도록 할수있습니다. 또한 요청을 수행하는 디비단이나 다른서버가 다른형식의 패킷을 보내면 시퀀스를 어겼다고 네트웍시스템이 감지할수있습니다.

     

구체적인 구조를다시 만들면

 

server 인터페이스 선언에

int login_packet(string id,string pass) ;  를 선언하고

 

int login_packet(string id, string pass)

{

int x;

yield db.query_login(id,pass) ; <= db 에 쿼리를 날리고 올때까지 알아서 기다려줌(그동안 다른 클라이언트핸들링은 처리됨)

 

  if( ret == 0)   // 결과에 따라 처리

 

yield return ret;  <= loing_packet 를 콜한 콜러에게 send 해줌 , 또한 함수 선언에 리턴값으로 int 를 준다고 했으므로 리턴이 없거나 다른형을날리면 네트웍 시스템이 인지하여 경고처리가 가능

}

 로 표현이 됩니다.  마치 일반 어플리케이션 코딩하듯이 하면된다는거죠

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

 작업하다보면 foreach 안에서 remove 를 할때가 꼭있다.

속도무시하고 편하게 사용하고 싶을때 사용하면 좋을듯하다.

어떤식으로 구현하는지 의도만 파악하고 자신의 입맛대로 바꾸길 바란다.

 

lib 코드

  1.  
  2. using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
  3. /// 버전 0.1
    /// perpet@hitel.net 구본수
    /// 소스 수정해서 사용하는건 문제 없으나 수정을 하면 반드시 원 제작자에게 수정된 소스를 메일로 보내야합니다. 버그나 개선된것을 꼭알려달라는 뜻입니다.
    ///
    /// 초기 버전이라 버그가 많을수있습니다..
  4.  
  5. namespace Foreach_safe_list
    {
  6.     class SafeCollection<TKey, TValue> //: Dictionary<TKey, TValue>
        {
            //원본리스트
            Dictionary<TKey, TValue> _List = new Dictionary<TKey,TValue>();
  7.         // 임시 add list
            Dictionary<TKey, TValue> _AddList = new Dictionary<TKey, TValue>();
  8.         // 임시 remove list
            List<TKey> _RemoveList = new List<TKey>();
  9.         public Dictionary<TKey, TValue>.KeyCollection Keys
            {
                get { return _List.Keys; }
           
  10.        }
  11.  
  12.         public void Clear()
            {
                if (bForeachCount == 0)
                {
                    _List.Clear();
                }
                else
                {
  13.                 _AddList.Clear();
                    _RemoveList.Clear();
  14.                 foreach(TKey key in _List.Keys)
                        _RemoveList.Add(key);
  15.             }
  16.         }
  17.        public Dictionary<TKey, TValue>.ValueCollection Values {
               get{return _List.Values;}
            }

  18.         public bool ContainsKey(TKey key)
            {
                // 제일 먼저 add list 에서 찾아봐야한다
                bool ret = _AddList.ContainsKey(key);
                if (ret == true)
                    return true;
                // 그다음 remove list 에서 찾아봐야한다.
                if (_RemoveList.Contains(key))
                {
                    return false;
                }
  19.             return _List.ContainsKey(key);
            }
  20.         public bool TryGetValue(TKey key, out TValue value)
            {
                if (bForeachCount == 0)
                {
                    return _List.TryGetValue(key, out value);
                }
                else
                {
                    // 제일 먼저 add list 에서 찾아봐야한다
  21.                 bool ret = _AddList.TryGetValue(key, out value);
                    if (ret == true)
                        return true;
  22.                 // 그다음 remove list 에서 찾아봐야한다.
                    if (_RemoveList.Contains(key))
                    {
                        return false;
                    }

  23.                 return _List.TryGetValue(key, out value);
                }
  24.             //return false;
            }

  25.   public void Add(TKey key ,TValue value)
      {
                if (bForeachCount == 0)
                {
                    _List.Add(key, value);
                }
                else
                {
                    _AddList.Add(key,value);
                }
  26.   }

  27.         public void Remove(TKey key)
            {
                if (bForeachCount == 0)
                {
                    _List.Remove(key);
                }
                else
                {
                    if (_AddList.ContainsKey(key))
                    {
                        _AddList.Remove(key);
                    }
                    else
                    {
                        if (!_List.ContainsKey(key) || _RemoveList.Contains(key))
                        {
                            throw new Exception("dosen't have key");
                        }
  28.                     _RemoveList.Add(key);
                    }
                   
                }
            }
           
            public void StartForeach()
            {
                bForeachCount++;
            }
  29.         public void EndForeach()
            {
                bForeachCount--;
                if (bForeachCount == 0)
                {
  30.                 // remove 먼저해야함
  31.                 foreach (TKey item in _RemoveList)
                    {
                        _List.Remove(item);
                    }
  32.                 _RemoveList.Clear();
  33.                 foreach (KeyValuePair<TKey, TValue> item in _AddList)
                    {
                        _List.Add(item.Key, item.Value);
                    }
                    _AddList.Clear();
                }
            }

  34.   public uint bForeachCount = 0;
  35.         public Dictionary<TKey, TValue>.Enumerator BaseGetEnumerator()
      {
                return _List.GetEnumerator();
      }
  36.   public IEnumerator GetEnumerator()
      {
       return new SafeEnumerator(this);
      }

  37.         class SafeEnumerator : IEnumerator, IDisposable
            {
                SafeCollection<TKey, TValue> _list;
                Dictionary<TKey, TValue>.Enumerator enumerator;
  38.             public object Current { get { return enumerator.Current; } }
                public bool MoveNext() {
  39.                 while (enumerator.MoveNext())
                    {
                        if (_list._RemoveList.Contains(enumerator.Current.Key))
                            continue;
                        return true;
                    }
  40.                 return false;
               
                }
                public void Reset() {
                    //enumerator...Reset();
                }
  41.             public SafeEnumerator(SafeCollection<TKey, TValue> list)
                {
                    _list = list;
                    enumerator = _list.BaseGetEnumerator();
  42.                 _list.StartForeach();
  43.             }
  44.             public void Dispose()
                {
                    _list.EndForeach();
                }
            }

  45.  }
    }

 

test code

 

  1.  sdsd
  2. using System;
    using System.Collections.Generic;
    using System.Text;
  3. namespace Foreach_safe_list
    {
  4.     class buff
        {
            public bool _flag;
            public buff(bool flag)
            {
                _flag = flag;
            }
          
            public void update(){}
          
           
        }
  5.  class Program
     {
  6.  
  7.   static void Main(string[] args)
      {
                SafeCollection<int, buff> a = new SafeCollection<int, buff>();
  8.             a.Add(1,new buff(true));
                a.Add(2,new buff(true));
                a.Add(3,new buff(false));
                a.Add(4,new buff(true));

  9.             foreach (KeyValuePair<int, buff> aaa in a)
       {
                    if( a.ContainsKey(2))
                        a.Remove(2);
  10.                 foreach (KeyValuePair<int, buff> bbb in a)
                    {
                        bbb.Value.update();
                        if (bbb.Value._flag == false)
                            a.Remove(bbb.Key);
                    }
                }
  11.             foreach (KeyValuePair<int, buff> aaa in a)
                {
                    if (a.ContainsKey(2))
                        a.Remove(2);
  12.                 foreach (KeyValuePair<int, buff> bbb in a)
                    {
                        if (!a.ContainsKey(2))
                            a.Add(2, new buff(true));
  13.                 }
  14.             }
  15.             foreach (KeyValuePair<int, buff> aaa in a)
                {
                    Console.WriteLine("{0}", aaa.ToString());
                }
  16.  
  17.   }
     }
    }
  18.  

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

2.퀘스트

스터디/C++,CLI,C#2009. 9. 14. 20:11

 

그렇다면 이것으로 간단한 퀘스트를 짜보고 간단한 핵심 코드 리뷰를 해보자

Demo2

일반적으로 기획자가 상상하는 단순한 퀘스트를 상상해보자.

1.유저가 npc 를 클릭한다.

2.유저에게 A 를 만나달라고 대화박스를 보인다

3.승낙을 하면 아이템을 준다.

4.A를 클릭하면 고맙다고 한다.

아주일반적인 퀘스트 내용이다.

 

이것을 정말 저런순서대로 코딩을 할수없을까?

 

밑에 코드를 보자

 

quest1 버튼을 누르면 밑에 코드가 돌아간다.

 

우선 퀘스트 코드를 보자

 [소스1]


  1.         public IEnumerator GetEnumerator()
            {

    // 이미 퀘스트를 받았나?

                if (_Owner._QuestInfo.Contains(1))
                {

  2.        

            yield return new NCondition.WaitMessage("여기서 뭐하나", 2);

                    yield break;
                }

  3.            // 부탁을 한다.

                NCondition.WaitMessage msg = new NCondition.WaitMessage("반갑군. 내부탁을 들어줄수있나?", 0);

                yield return msg;

  4.            
  5.             if (msg.Ret() == "OK")
                {

                // ok 이면 퀘스트를 준다

                    _Owner._QuestInfo.Add(1);

                    yield return new NCondition.WaitMessage("고맙군 어서 친구를 찾아주게 ", 2);
                }
                else
                {

  6.                //

        yield return new NCondition.WaitMessage("실망일세 꺼지라우", 2);

                    yield break;
                }

  7.             // A를 클릭하면

                yield return new NCondition.WaitClickEvent(1);

  8.          
  9.             // 보상을 해주던지 다른이벤트를 하던지 하여간 여기서 끝냄

                yield return new NCondition.WaitMessage("반갑군 자네가 그친구인가? 너무늦었네... ", 5);


                yield break;
            }

     

     


 

한마디로 yield 로 표현된 코드는 우리가 일일히 스테이트관리를 해야하는작업을 코드가 알아서 만들어준다고 보면된다.

우리는 귀찮은 스테이트 관리와 데이타 관리를 컴파일러에게 넘겨주고 우리가 필요한 컨텐츠 코드작성만 하면되는것이다.

 [소스2]

  1.  
            public void Update()
            {
  2.             // 등록된 enumerable 오브젝트를 가지고 있다.
                // for 문중 ProcessList 에 있는 아이템을 지울수있는 구조를 위에 뒤에서 앞으로 for 문을 돈다.(성능은 좋지않다)
               
                for (int index = ProcessList.Count - 1; index >= 0; index--)
                {
                    JobInfo pProcess = ProcessList[index];
  3.                 // 현재 스크립트용 함수에서 yield return new icondition 류가 있는지 본다.
                    if (pProcess.m_Condition != null)
                    {
                        // yield return new icondition 이 있으면 완료가 될때까지 스킵한다.
                        if (pProcess.m_Condition.IsComplete() == false)
                        {// 선행작업이 완료되지 않았다.
                            continue;
                        }
                    }
  4.                 // yield 나 yield return 이 올때까지 프로세스가 진행된다.
                    if (pProcess.m_ProcessEnum.MoveNext() == true)
                    {
                        // 유저가 요정한 yield return new icondition 을 저장한다.
  5.                     pProcess.m_Condition = (ICondition)pProcess.m_ProcessEnum.Current;
                        continue;
                    }
                    else
                    {
                        // enumerable 문을 다 완료하였다면 지운다.
                        ProcessList.Remove(pProcess);
                    }
                }
  6.         }

위[소스2] 코드는 [소스1] 의 코드를 어떻게 제어할수있는지 보여주는 코드이다.

이샘풀소스에 위코드 두가지를 이해한다면 핵심적인 내용은  다이해한거라 볼수있다.


다음에는 이 yield 프로그래밍을 가지고 분산프로그램을 쉡게 구현할수있는 방법을 소개해 보겠다.

이 글은 스프링노트에서 작성되었습니다.

YIELD 키워드(lua coroutine)

이글은 c# 을 스크립트나 병렬처리를 하기위한 기초지식을 위한 글이다..

 c# 을쓰면서 yield 키워드를 쓰는일이 거의 없을수도 있다. 그것은 필요가 없는것이 아니라 그용도와 사용방식을 잘 모르기때문인것 같다.

특히나 이 yield  키워드는 c++ 에 없는 키워드이기에 c++ 프로그래머 들에게는 너무 생소하다.

(c++ 에서  Fiber 라는 것이 있다고 한다.나는 써보지를 않아서....)

 

나또한 이 개념을 알게 된 계기도 서버에서 스크립트로 lua 에서 coroutine 개념을 처음 써보고 후에 c# 에서 이 개념을 써보고 싶어서 자료를 찾던중 yield  라는 키워드로 제공되는것을 알았다.

우선

 

http://asyncsample.googlecode.com/files/AsyncSample.zip

 

에서 소스를 다운받는다.

다음 Enumerable 솔루션 파일을 열고

(ms visual c# 2008 express  http://www.microsoft.com/express/download/ )

 
Demo0 프로젝트를 본다.


우선 이 yield 키워드가 어떠한 일을 하는지 보고 이것을 가지고 어떠한 곳에서 사용할수있는지 보자.

우선 핵심적인 내용은 TestCode1() 함수안에 있는 코드의 진행을 IEnumerator.MoveNext() 라는 함수로 제어를 할수있다는것이다.


루아를 써보신분은 쉽게 접근을 할수있겠지만 코루틴을 처음 보시는분은 아직도 감이 오지않을것이다.

c++ 프로그래머 이라면 과연 저것이 내부적으로 어떻게 움직이는지 궁금할것이다.

 

http://www.lutzroeder.com/dotnet/

 

여기 사이트에 들어가서 reflector/ 프로그램을 다운받고 위에 샘풀코드 실행파일을 역어셈해보자

역어셈코드를 보면 코드가 어떻게 움직이는지 바로 느낄수있을거다.

 

원래 코드이다

 

  1.  
            public static IEnumerable TestCode1()
            {
  2.             int x = 3;
  3.             yield return x;
                x++;
                yield return x;
                x++;
                yield return x;
                x++;
                yield return x;
                x++;
  4.             yield break;
  5.         }

 

 .net Reflector 를 사용하여 위코드가 어떻게 변환되였는지 보자

 

  1.  

    private bool MoveNext() 
    {
  2. switch (this.<>1__state)
    {
    case 0:
    this.<>1__state = -1;
    this.<x>5__1 = 3;
    this.<>2__current = this.<x>5__1;
    this.<>1__state = 1;
    return true;
    case 1:
    this.<>1__state = -1;
    this.<x>5__1++;
    this.<>2__current = this.<x>5__1;
    this.<>1__state = 2;
    return true;
    case 2:
    this.<>1__state = -1;
    this.<x>5__1++;
    this.<>2__current = this.<x>5__1;
    this.<>1__state = 3;
    return true;
    case 3:
    this.<>1__state = -1;
    this.<x>5__1++;
    this.<>2__current = this.<x>5__1;
    this.<>1__state = 4;
    return true;
    case 4:
    this.<>1__state = -1;
    this.<x>5__1++;
    break;
    }
    return false;
    }

위코드가 변화되는것이 흥미롭다면  Demo01 도 한번 컴파일 하고 Reflector 로 보기 바란다.

 

그런데 과연 이것이 어떠한 코드표현에 유용한지 이해가 안간다면

우선  gpg 5권을 10.4 단락을 읽어보기 바란다 거기서 표현하고자 하는 의도와 여기서 말하는것은 다른것이 없다.

 

핵심적인 내용은 순차적인 어떠한 서술내용을 프로그램밍으로 직관적으로 표현할수있다라는것이다.

이것이 앞으로 이해할 핵심적내용이다 이 서술적 프로그래밍은 앞으로 이해할 프로그래밍을 설명하는 핵심적 문구라 볼수있다.

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

공부한게 아까워서 정리하는차원에 wpf 버전으로 만든다.
http://www.codeproject.com/KB/recipes/Shortest_Path_Problem.aspx 문서보고 만듬.

실버라이트 어풀을 만들고 여기다 올려보자..
wpf 도 테스트

저번 프로젝트에서 lua 가 유행을하여..
나도 한번 스크립트의 효율성의 느껴보고자..이것저것 많은곳에 lua 를 써보았다..
확실히 느낀건 많은구현을 쉽고 유연하게 구현할수있다는느낌을 받았다..
개인적으로 단점으로 느낀건 디버깅과 타이핑이 조금 짜증스러웠다..당연 비주얼스튜디오 환경에
너무 익숙해져 있는 이유인것 같기도하다..
그러다.. 몇달전 gpg 에서 스크립트로 C# 을 쓴다는 글을 보고.. C# 이라면 내가 부족하다고 느끼던
많은부분을 더충족시켜주지 않을까 생각하고 C# 을 이용한 스크립트 구현방식을 구글님을 통해
여러 정보를 얻어봤다..
스크립트라면 꼭필요한것으로 동적바인딩과 루아에서 yield 의 기능이 꼭필요한데..
C#으로 모든것이 표현가능하고 거기다 vs 로 편집및 디벙깅도 가능하니...
모든 서버 모듈을 c# 으로 짜고싶어지는 느낌이 들었다..
예전에 이브 온라인이 거의 모든것을 스크립트로 짰다고 한다..

http://www.slideshare.net/Arbow/stackless-python-in-eve/

현재 나의 목표는 이브 온라인과 같은 구조를 C#으로 만드는것이다..
허나 업무에 치여 바라만 보고있음...