이번주에 또 프로젝트가 중단되었다.

런칭했던 게임은 성적이 저조하여 빠른 중단. 대부분의 팀원들은 다른 프로젝트로 이동. 일부가 남아서 신규 프로젝트 제안 시작.

이렇게 진행 되는 와중에 신규 개발 경영진이 교체되고 이러저러한 일이 있었다.
그리고 신규 제안은 제안을 하기도 전에 중단. 다른 라이브 프로젝트로 합류.

곰곰히 생각해 봤을 때 아쉬운건.. 내가 몸담고 있던 조직은 2015년에 시작했던 프로젝트를 어느정도 마무리 지은 이후에는, 새로운 프로젝트를 시작할 기회가 없었다는 것 인것 같다.
처음부터 시작해서 결과를 보고 마무리 한 뒤 다른 프로젝트로 이동이면 납득할텐데..

두번이나 언제나 남들이 하던거 이어 받아서 하면서… 적응하느라 고생하고, 욕 들어먹고, 성과는 없었으니 아쉬움이 많다.

분명 열심히 하다보면 또 재미있는 프로젝트를 할 기회가 오긴 할 것이다. 하지만 그래도 지난 몇년간이 아쉬운게 사실이다. 그리고 또 다시 적응하느라 고생할 생각을 하니 만사가 귀찮다.

C# Visitor

바로 이전글인 double dispatching 이랑 비교해서 생각해보자

using System;
using System.Collections.Generic;

namespace ConsoleApp3
{
    // 1. Cat, Dog 간 Visitor 구현 코드
    public interface IVisitableElement
    {
        void Accept(IFindSameVisitor visitor);
    }

    public class Animal
    {
    }

    public class Cat : Animal, IVisitableElement
    {
        public void Accept(IFindSameVisitor visitor) => visitor.Visit(this);
    }

    public class Dog : Animal, IVisitableElement
    {
        public void Accept(IFindSameVisitor visitor) => visitor.Visit(this);
    }

    public partial interface IFindSameVisitor
    {
        void Visit(Cat visitor);
        void Visit(Dog visitor);
    }

    public partial class FindSameCat : IFindSameVisitor
    {
        public void Visit(Cat visitor)
        {
            Console.WriteLine("Cat-Cat = Same");
        }

        public void Visit(Dog visitor)
        {
            Console.WriteLine("Cat-Dog = Cute");
        }
    }

    public partial class FindSameDog : IFindSameVisitor
    {
        public void Visit(Cat visitor)
        {
            Console.WriteLine("Dog-Cat = Cute");
        }

        public void Visit(Dog visitor)
        {
            Console.WriteLine("Dog-Dog = Same");
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            // 1차 구현 실행
            var animals1 = new List<IVisitableElement>
            {
                new Cat(), new Dog()
            };

            var catVisitor = new FindSameCat();
            var dogVisitor = new FindSameDog();

            animals1.ForEach(a => a.Accept(catVisitor));
            animals1.ForEach(a => a.Accept(dogVisitor));

            Console.WriteLine();

            // 2차 Mouse 추가 구현 실행
            var animals2 = new List<IVisitableElement>
            {
                new Cat(), new Dog(), new Mouse()
            };

            var mouseVisitor = new FindSameMouse();

            animals2.ForEach(a => a.Accept(catVisitor));
            animals2.ForEach(a => a.Accept(dogVisitor));
            animals2.ForEach(a => a.Accept(mouseVisitor));
        }

    }


    // 2. Mouse가 추가된 경우 추가 구현 코드
    public class Mouse : Animal, IVisitableElement
    {
        public void Accept(IFindSameVisitor visitor) => visitor.Visit(this);
    }

    public partial interface IFindSameVisitor
    {
        void Visit(Mouse visitor);
    }

    public partial class FindSameCat : IFindSameVisitor
    {
        public void Visit(Mouse visitor)
        {
            Console.WriteLine("Cat-Mouse = Mustache");
        }
    }

    public partial class FindSameDog : IFindSameVisitor
    {
        public void Visit(Mouse visitor)
        {
            Console.WriteLine("Dog-Mouse = Four Legs");
        }
    }

    public partial class FindSameMouse : IFindSameVisitor
    {
        public void Visit(Mouse visitor)
        {
            Console.WriteLine("Mouse-Mouse = Same");
        }

        public void Visit(Cat visitor)
        {
            Console.WriteLine("Mouse-Cat = Mustache");
        }

        public void Visit(Dog visitor)
        {
            Console.WriteLine("Mouse-Dog = Four Legs");
        }
    }
}

C# Double Dispatch

using System;

namespace ConsoleApp3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Animal a = new Cat();
            Animal a1 = new Cat();
            Animal b = new Dog();
            Animal b1 = new Dog();
            Animal c = new Mouse();
            Animal c2 = new Mouse();

            a.FindSamething(a1);
            a.FindSamething(b);
            a.FindSamething(c);
            Console.WriteLine();

            b.FindSamething(a);
            b.FindSamething(b1);
            b.FindSamething(c);
            Console.WriteLine();

            c.FindSamething(a);
            c.FindSamething(b);
            c.FindSamething(c2);
            Console.WriteLine();
        }

    }

    // 1. Cat, Dog 간 Double Dispatching 이 필요한 경우 구현 코드
    partial class Animal
    {
        public virtual void FindSamething(Animal animal) => Console.WriteLine("Animal FindSamething");

        public virtual void FindSamethingImpl(Cat obj) => Console.WriteLine("Animal-Cat FindSamethingImpl");
        public virtual void FindSamethingImpl(Dog obj) => Console.WriteLine("Animal-Dog FindSamethingImpl");
    }

    partial class Cat : Animal
    {
        public override void FindSamething(Animal animal)
        {
            animal.FindSamethingImpl(this);
        }

        public override void FindSamethingImpl(Cat obj) => Console.WriteLine("Cat-Cat = Same");
        public override void FindSamethingImpl(Dog obj) => Console.WriteLine("Cat-Dog = Cute");
    }

    partial class Dog : Animal
    {
        public override void FindSamething(Animal animal)
        {
            animal.FindSamethingImpl(this);
        }

        public override void FindSamethingImpl(Dog obj) => Console.WriteLine("Dog-Dog = Same");

        // 이렇게 리다이렉션 하는 경우 호출객체와 인자가 뒤바뀌기 때문에
        // this.FindSamethingImpl 와 obj.FindSamethingImpl 의 결과가 다른 경우에는 별도 구현해야함.
        public override void FindSamethingImpl(Cat obj) => obj.FindSamethingImpl(this);
        
    }






    // 2. Mouse가 추가된 경우 추가 구현 코드
    partial class Animal
    {
        public virtual void FindSamethingImpl(Mouse m) => Console.WriteLine("Animal-Mouse FindSamethingImpl");
    }
    partial class Cat
    {
        public override void FindSamethingImpl(Mouse m) => m.FindSamethingImpl(this);
    }

    partial class Dog
    {
        public override void FindSamethingImpl(Mouse m) => m.FindSamethingImpl(this);
    }

    partial class Mouse : Animal
    {
        public override void FindSamething(Animal animal)
        {
            animal.FindSamethingImpl(this);
        }

        public override void FindSamethingImpl(Cat c) => Console.WriteLine("Mouse-Cat = Mustache");
        public override void FindSamethingImpl(Dog d) => Console.WriteLine("Mouse-Dog = Four Legs");
        public override void FindSamethingImpl(Mouse m) => Console.WriteLine("Mouse-Mouse = Same");
    }

}

C++ variant 기초 사용 & 상속 캐스팅과 비교

#include <iostream>
#include <variant>
#include <vector>
#include <chrono>

using namespace std;

struct Object
{
};

struct Object0 : public Object
{
	int value;
};

struct Object1 : public Object
{
	vector<int> values;
};

struct Object2 : public Object
{
	string values;
};

struct Object3 : public Object
{
	wstring values;
};

int main()
{
	variant<Object0, Object1, Object2, Object3> var;

	cout << "size" << endl;
	cout << "Object0 : " << sizeof(Object0) << endl;
	cout << "Object1 : " << sizeof(Object1) << endl;
	cout << "Object2 : " << sizeof(Object2) << endl;
	cout << "Object2 : " << sizeof(Object3) << endl;
	cout << "variant : " << sizeof(var) << endl;

	var = Object0(); // 이 코드가 없으면 아래 값을 설정할때 에러가 발생함. variant의 타입이 정해지지 않기 때문인듯.
	get<Object0>(var).value = 0;
	cout << "variant Object0 value: " << get<Object0>(var).value << endl;

	constexpr int COUNT = 100'000'000;

	{
		auto caseStr = "case variant"s;
		auto startTime = chrono::steady_clock::now();

		volatile int64_t accValue = 0;
		for (int i = 0; i < COUNT; i++)
		{
			Object0& rObj = get<Object0>(var);
			rObj.value = i;
			accValue += rObj.value;
		}

		auto endTime = std::chrono::steady_clock::now();

		cout << format("{}({}) : {}ms, accValue={}", 
			caseStr, 
			COUNT, 
			chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count(), 
			(int) accValue) << endl;
	}

	{
		Object* pObj = new Object0();

		auto caseStr = "case pointer"s;
		auto startTime = chrono::steady_clock::now();

		volatile int64_t accValue = 0;
		for (int i = 0; i < COUNT; i++)
		{
			Object0& rObj = *(static_cast<Object0*>(pObj));
			rObj.value = i;
			accValue += rObj.value;
		}

		auto endTime = std::chrono::steady_clock::now();

		cout << format("{}({}) : {}ms, accValue={}",
			caseStr,
			COUNT,
			chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count(),
			(int)accValue) << endl;
	}

	return 0;
}
size
Object0 : 4
Object1 : 24
Object2 : 32
Object2 : 32
variant : 40
variant Object0 value: 0
case variant(100000000) : 47ms, accValue=887459712
case pointer(100000000) : 44ms, accValue=887459712

variant가 사알짝 느림.
하지만 한번 설정한 타입에 대해서 이후 다른 타입으로 접근시 익셉션을 발생시켜 준다는 점에서 안정성은 좋을듯.

타입세이프 유니온이라는 이름에 걸맞는듯.

아이 키우기 감상

이제 아이가 태어난지 5년이 되었다.
새삼 이런 저런 생각이 들어 한번 글로 써 보았다.

나는 인생이 여러 개의 원을 그리고 있다고 생각한다. 내가 산 삶 중 생활 방식에서 큰 변화가 있었던 시기를 원으로 표현하면, 잘 기억나지 않지만 잘 뛰어놀았던 것 같은 유년기의 원, 친구들과 놀고 게임을 많이 했던 초등학교, 중학교의 원, 나름 수험 생활을 했던 고등학교의 원, 스스로 공부하고 또 게임을 즐겨했던 대학교의 원, 그리고 직장 생활의 원.

결혼을 하지 않고, 아이를 낳지 않았으면 직장 생활의 원에서 더 이상 새로운 것을 찾지 못하는 원을 빙글빙글 돌고 있지는 않았을까? 어쩌면 개인적 성공이나 어떤 일로 다른 원을 그리게 되었을 지도 모르겠다. 하지만 확실한 것은 아이와 그리는 부모로서의 원은 내가 자라나면서 그렸던 원과 전혀 다르다. 마치 아이가 그리고 있는 원의 바깥을 둘러싸고 따라가는 원을 그리고 있는 것 같다.

어쩌면 이런 원을 그리는 것이 아이가 위주로 된 삶처럼 보일 수도 있겠지만, 아이가 그리는 원을 보면서 나의 새로운 원을 그리는 것은 언제나 일상 속에 새로움이 있다.

여행을 떠나서 다시 집으로 돌아와 소파에 누우면서 역시 집이 최고라고 말하는 내 성향 때문일지도 모르겠지만 , 새로운 것으로 느끼는 재미와 즐거움이 지겨운 일상이 있지 않으면 느낄 수 없는 것이라는 것을 이제는 알고 있다.

아이는 아마 스스로의 원을 그리겠지. 아이는 부모가 언제나 옆에 있는 것이 당연하여 그 원 주변에서 함께 원을 그리는 것을 잘 모를 것이다. 내가 그랬던 것 처럼. 나는 아이가 원을 그리는 것을 멀리서 지켜볼 때 도 있고, 가끔은 원 모양이 너무 찌그러지지 않게 잔소리도 할 것이고, 다른 사람이 아이가 그리는 원을 망치지 않도록 나서서 막아주는 원을 그릴 것이다. 나의 부모님이 그러하셨던 것 처럼.

C++ vector reallocate action with noexcept

#include <iostream>
#include <vector>

class MyClass
{
private:
    std::string _name{};

public:
    MyClass(const std::string& name)
        :_name(name)
    { 
        std::cout << "create : " << _name << std::endl;
    }

    MyClass(const MyClass& other) noexcept
    {
        std::cout << "copy : " << other._name << std::endl;
    }

    // "noexcept"가 없으면 move가 아닌 copy가 불린다.
    MyClass(MyClass&& other) noexcept
    {
        std::cout << "move : " << other._name << std::endl;
    }
};

int main() {
    std::vector<MyClass> list;
    list.reserve(2);
    list.emplace_back("a1");
    list.emplace_back("a2");
    list.emplace_back("a3");
}

C++ 시간함수 성능비교 #1

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <atomic>
#include <windows.h>

auto main() -> int
{
    std::cout << "start" << std::endl;
    constexpr int COUNT = 10000000;
    
    {
        auto startTime = std::chrono::steady_clock::now();
        long long accCount = 0;

        for (int i = 0; i < COUNT; i++)
        {
            volatile auto now = std::chrono::steady_clock::now();
        }
        auto endTime = std::chrono::steady_clock::now();

        std::cout << "std std::chrono steady clock : "
            << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
            << "ms" 
            << ", accCount : " << accCount << std::endl;
    }

    {
        auto startTime = std::chrono::steady_clock::now();
        auto initTime = std::chrono::system_clock::now();
        long long accCount = 0;

        for (int i = 0; i < COUNT; i++)
        {
            volatile auto now = std::chrono::system_clock::now();
        }

        auto endTime = std::chrono::steady_clock::now();
        std::cout << "std std::chrono system clock : "
            << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
            << "ms" << std::endl;
    }

    {
        auto startTime = std::chrono::steady_clock::now();
        
        SYSTEMTIME stInit;
        GetSystemTime(&stInit);
        long long accCount = 0;

        for (int i = 0; i < COUNT; i++)
        {
            SYSTEMTIME stNow;
            GetSystemTime(&stNow);
            volatile WORD sec = stNow.wSecond;
        }

        auto endTime = std::chrono::steady_clock::now();
        std::cout << "windows  : "
            << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
            << "ms" 
            << ", accCount : " << accCount << std::endl;
    }

    {
        auto startTime = std::chrono::steady_clock::now();

        bool isShutdown = false;
        std::atomic<SYSTEMTIME> stNow;
        auto timer = std::thread([&isShutdown, &stNow]() {
            while (isShutdown == false)
            {
                {
                    SYSTEMTIME now;
                    GetSystemTime(&now);
                    stNow = now;
                }
                ::Sleep(0);
            }
            });

        for (int i = 0; i < COUNT/10; i++)
        {
            SYSTEMTIME now = stNow;
            for (int i = 0; i < 10; i++)
            {
                volatile WORD sec = now.wSecond;
            }
        }

        auto endTime = std::chrono::steady_clock::now();
        std::cout << "Threading windows  : "
            << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
            << "ms" << std::endl;

        isShutdown = true;
        timer.join();
    }
}
Visualstudio 2019 16.9.2 Release x64

음… 루프 숫자를 올렸을때 시간이 선형적으로 증가하는걸로 봐서.. 대충 최적화 되서 함수콜이 날아가 버리지 않고 정직하게 호출됬다고 생각됨.

Threading windows는 시간 관련 함수의 성능 때문에 스레드로 빼서 시간을 업데이트하는 스레드가 별도로 있고 다른 스레드는 그 값을 가져와서 쓰는 부분을 모사한것. (일반 콜에 비해서 시간 업데이트는 가끔. 여러 스레드가 업데이트된 값을 가져다가 쓰는 형태)

chrono의 성능은 나쁘지 않음.
시간이 살짝 부정확 하더라도 함수 콜 숫자를 줄이고 싶다면 스레딩

VisualStudio 빌드 이벤트

뭐 복사하거나 그런거.. 기본적으론 한줄만 넣을수 있고, 여러 동작을 처리하려면 스크립트 파일을 실행하도록 해야 할것 같은데… 꼼수로 두줄이상 넣는방법

기본 한줄
xcopy /y /d /e “$(SolutionDir)configs\Common*” “$(OutDir)”

두줄
xcopy /y /d /e “$(SolutionDir)configs\Common*” “$(OutDir)” & xcopy /y /d /e “$(SolutionDir)configs\$(ProjectName)*” “$(OutDir)”
명령 사이에 & 를 넣는다.

C++ filesystem replace_extention

BOOL Program::LoadJsonConfig()
{
	namespace fs = std::filesystem;

	wchar_t exePathStr[4096];
	GetModuleFileName(NULL, exePathStr, 4096);
	fs::path exePath = exePathStr;
	fs::path jsonConfPath = exePath.replace_extension("config.json");

	if (fs::is_regular_file(jsonConfPath) == false)
	{
		return FALSE;
	}

	std::ifstream jsonConfFile(jsonConfPath);
	try
	{
		jsonConfFile >> m_jsonConfig;
	}
	catch (...)
	{
		jsonConfFile.close();
		return FALSE;
	}

	jsonConfFile.close();
	return TRUE;
}

C++11에 추가된 filesystem으로 실행된 exe과 동일한 이름의 config.json 파일을 읽는 코드.

c++ filesystem 은 이제 다른 스크립트언어에 지원하는 file 처리 내용과 유사하게 지원해 줘서 c++ 에서 이런 저런 내용 처리하기가 많이 편해진것 같다.
예전에 텍스트 파일 일괄 처리할 일이 있었는데 c++에서 정규표현식도 지원되고 파일시스템도 지원되니 가끔 써서 어색한 스크립트보다 그냥 자주써서 편한 C++로 작성하고 돌림.

작성 후 컴파일 해서 적용해야 한다는게 단점이지만 실행속도가 빠르니 대용량 처리할때는 나름 장점이 있다.