TeamSOC / SOCEngine

MIT License
4 stars 0 forks source link

Key Observer #68

Closed Jin02 closed 6 years ago

Jin02 commented 6 years ago

From @sixzone11 on January 24, 2017 12:12

Camera Hashing Key Observer

Camera Hashing Key가 Camera 인스턴스의 직접적인 address로 구현된 점은 map을 이용한 탐색에서 선형이하 탐색 시간에 획득할 수 있게 해주는 이점이 있지만, 해당하는 인스턴스가 프로그램의 실행 주기 안에서 어떤 변화를 겪을지 예측할 수 없으므로 직접적인 주소를 키로써 사용하는 것은 위험할 수 있다.
이에 대응하기 위한 적절한 방법들은 다음과 같다.

  1. String key
    임의의 string으로 key를 대신하는 것으로, 생각했던 것보단 빠르게 동작할 것이다. 다만, 이 키를 사용자 입장에서 기억해야 한다는 단점이 존재한다.
  2. Assert immortal
    프로그램의 라이프 사이클동안 절대 생성된 인스턴스가 제거될 일이 없음을 확실히 한다. 이 경우는 string key를 이용할 때 발생할 조금의 오버헤드도 용납하고 싶지 않을 때 확실히 유용할 것이다. 다만, 프로젝트를 확장해가는 과정에서 이 assertion이 발목을 잡게 될 수도 있다.
    (물론 내 개인적인 경험 상 한 번 이런 용도로 사용된 코드들은 그렇게 용도가 변경될 일이 거의 없기도 하다.)
  3. Observe and notify
    인스턴스의 상태가 변화될 때마다 해당 인스턴스 스스로 변화됨을 notify하고 이 인스턴스의 주소를 멤버로 가지고 있는 모든 객체들이 멤버의 값을 바꾸도록 (setter따위를 이용해서) 구현할 수 있다.
    이를 위해선 각 인스턴스마다의 observer가 추가적으로 필요하고, 이를 인스턴스가 접근할 수 있는 매커니즘이 존재해야한다. 이를 위해서 매니저 클래스가 만들어져야할 것이며 아마 현재 사용 중인 map 구조를 하나 더 사용해서 매니저에 접근하든가, 인스턴스의 private 멤버로 추가하거나 해야 할 것이다. 매니저는 내부에 이 인스턴스를 사용하고자 하는 모든 대상들(users)을 컨테이너에 넣어 관리하고 있다가 인스턴스가 대체될 때 users[i]->ReplaceInstance( newInstance, oldInstance ) 따위의 방법으로 새로운 인스턴스를 사용하게할 수 있다.
    말로만 써도 엄청 복잡하니까 샘플 코드를 작성해보긴 하겠다만, 쓸데없이 복잡하고 효율성도 별로 안 좋을 것 같다. 애초에 옵저버 패턴을 고작 이런 변수값 고치는 정도에 쓰는 건 낭비이므로 가급적 이런 일은 회피하는 게 좋을 것이다. (그래도 심심하니까 코드는 써보겠음)

    class Instance;
    class User {
    public:
    virtual void ReplaceInstance( Instance* newInstance, Instance* oldInstance ) = 0;
    };
    class Manager {
    Instance* instance;
    vector<User> users;
    public:
    void SetInstance( Instance* newInstance ) {
        for( auto u : users )
            u.ReplaceInstance( newInstance, instance );
    }
    };
    //...
    Instance* ReplaceInstance( Instance* oldOne ) {
    // create new instance
    Instance* newOne = new Instance;
    
    // copy from old to new with operator =
    newOne = oldOne; // or use copy-constructor
    
    // get manager from your own way.
    // ex1) GetManager( Instance* one) { return one->getManager(); }
    // ex2) GetManager( Instance* one) { return g_managerTable[ GetManagerKey( one ) ]; }
    Manager* oldManager = GetManager( oldOne );
    
    // Manager::SetInstance() should be called in ::SetManager().
    // It wouldn't be needed that pass a ownership of manager if it was done in copying.
    SetManager( newOne, oldManager );
    
    // delete old instance
    delete oldOne;
    
    return newOne;
    }
    //...
    // VectorMap replace case
    class VectorMap : User {
    // ...
    // Implemented its own mechanism.
    vector<Instance*> contigousInstances;
    map<address, Instance*> labeledInstances;
    // ...
    public:
    void ReplaceInstance( Instance* newOne, Instance* oldOne )
    {
        labeledInstances[ reinterpret_cast<address>(oldOne) ] = newOne;
    }
    };
  4. Capsulate instance value에 보존할 인스턴스가 다른 것으로 대체될 때 key가 무효화되는 것이 문제라면, 해당 key로써 스마트 포인터의 주소를 사용하고, 인스턴스 본인은 스마트 포인터에 의해 관리되는 것도 고려해볼 법하다.
    인스턴스의 생성 및 제거 등 지저분한 일들은 내부에서 어떻게든 하고, 외부에 드러내놓는 것은 스마트 포인터라는 껍데기이기 때문에 실제 인스턴스가 다른 것으로 대체되더라도 외부에선 전혀 그 사실을 모를 것이다.
  5. Re-design 애당초에 이런 식으로 만들어져야 하는지 자체를 다시 검토해보는 것이 좋을 수도 있다. 이미 주소를 알고 있다는 시점에서 이것이 map으로 관리되어야 할 대상인지, 혹시 지나친 객체지향주의에 사로잡혀서 관리할 필요도 없는 것을 관리하려 드는 것은 아닌지 진지빨고 생각해볼 수 있다.
    개인적으로 보기엔 후자일 가능성이 커보인다.

생각할 수 있는 여러 가지 방법들을 읊어보긴 했는데 사실 전체 코드가 어떤 매커니즘으로 굴러갈지 다 보질 못해서 어떤 걸로 하라고 딱 정해서 말을 못하겠다. 그냥 이런 저런 생각은 해봤으니까 검토해서 반영하는데 도움이 되는 쪽이길 바란다.

Copied from original issue: Jin02/SOCEngine#68

Jin02 commented 6 years ago

60 처리하면서 같이 처리할 예정.

Jin02 commented 6 years ago

https://github.com/Jin02/SOCEngine/blob/%23Re/SOCEngine/SOCEngine/Core/ObjectId.hpp https://github.com/Jin02/SOCEngine/blob/%23Re/SOCEngine/SOCEngine/Core/ObjectIdManager.h

좀 갈아엎은 시간이 지나긴 했지만.. 여튼 갈아엎긴 했다. 아마 잘 돌아갈 것 같다.

그리고 ObjectIdManager에서 bitset 말고 그냥 uint로 처리하는게 더 효율적일 것 같다. 이건 그냥 나중에 시간나면 고치면 된다.