28 февр. 2010 г.

Полнота по Коши

Предлагаю, по аналогии с полнотой по Тьюригну, языки полные матана (Haskell) называть полными по Коши :)

8 февр. 2010 г.

Ruby & Co

Во-первых решил запилить на Ruby обработку медиатеки. Успешно написал стягивание обложек с ласта. Дальше чего-то забил.

Во-вторых по случаю поднял дома сервак из ешки (apache + passenger -> redmine) и сделал внешний IP.
Выводы: redmine рулит. Не ставте passenger из репозитариев ubuntu =) Не пользуйте горком =)

С++ с конкурентностью и акторами (подобием оных)

Далее будет мой концепт на тему. Разрабатываю главным образом в рамках этого
Заренее прошу прощения за поехваший код. Я хз как его заставить нормально отображаться :(

Ниже я изложу некоторое количество мыслей на тему in-game объектов, сервисов, поддвижков и того, как это параллелить. Излагать буду в следующем порядке:
1) Абстрактно в отрыве от всего, о модели взаимодействия.
2) Как описанное в предшествующем пункте ложится на нашу задачу, как это пользовать и как мешать с plain С++ кодом. В отрыве от реализации.
3) Как это будет реализовано.

1) Абстрактное
Давайте на время забудем про нашу задачу, про наш С++ и про всё остальноё, отхлебнём хорошего чая и погрузимся в сабж.
Итак мы хотим (просто хотим, срочно забыли про задачу!) чтобы в конкурентном, возможно распределённом, окружении существовали и взаимодействовали какие-то артефакты, и чтобы было быстро. Обременив себя такими желанием мы непременно изобретём Erlang и Actor-model. Немного приспустившись на бренную землю (вот тут можно вспомнить, что мы делаем игру и что мы не Naughty Dog) нам захочется во-первых чего-то более знакомого и императивного, потому, что надо писать игру, а не учиться ФП (оно жутко полезно, но не в проекте с приходящими и уходящими кадрами); во-вторых нам захочется чего-нибудь менее асинхронного. Рассуждая, в таком ключе я пришёл к некоторой компромиссной модели.
Вот теперь вновь пришло время позабыть про то, что мы делаем, в противном случае в голове вашей будут роится мысли о том, оно как на мою модель ложится, которые, возможно, будут мешать воспринимать саму модель. Вначале обратимся к конкурентности, и введём понятие процесса (process), способного к выполнению полезной работы (О том как красавца трудоустроить мы поговорим немного позже). Процессы на реальные потоки ложатся как-то, нас это пока не волнует, нам сейчас важно, знать что они могут выполняться одновременно. Ну а "какие-то артефакты" назовём объектами (objects), типизированы объекты... странно они типизированы =) А строго-говоря не типизированы вовсе. Чтобы как-то различать поганцев будем помечать каждый тегами (tag), прямо как сообщения в вашем блоге. По тегу можно выбирать коллекции объектов, и производить с ними операции. Для коммуникации воспользуемся телеграфом^W сообщениями (message). Сообщение можно послать как кому-то так и широковещательно. На сообщение можно ответить банально вернув результат. Для взаимодействия один объект посылает сообщение другому (это близко к вызову метода, но не совсем), где-то за кулисами что-то происходит (а может и не происходит), и вот сообщение доставлено, вызывается реализация (implementation). А вернее реализации, на каждое сообщение можно навесить сколько душе угодно реализаций, они все будут вызваны, в недетерминированном порядке (в т.ч. параллельно). Если реализация не определена, то ничего не произойдёт (но можно ловить warnig'и при отладке), а в качестве результата вернётся гордый 0 (NULL/nil/...).
Но пока объекты сами по себе чертовски глупы (единственно обучены ссылки считать, впрочем это уже детали), учить их уму (ма)разуму вестимо задача программиста. Для этого придумаем расширения (extension). Расширением называется комбинация из данных (datum) и реализаций, сопоставленных паре {tag, process}. В момент регистрации расширения (она может произойти и в run-time) все объекты, отмеченные тегом, к которому привязывается расширение, обучаются обработке сообщений, реализации которых определены в расширении (либо реализации добавляются к уже существующим). Как не трудно догадаться, когда объекту приходит сообщение, реализации исполняются в соответствующих процессах. Сообщения обрабатываемые одним процессом никогда не обрабатываются одновременно. Ни между тегами, ни между процессами данные не разделяются вообще, синхронизация получается за счёт нескольких обработчиков одного сообщения. Избавившись от разделяемого ресурса, мы избавляем себя и от необходимости контроля доступа (mutex'ы и прочее), идея эта не нова и за годы себя зарекомендовала.
Важно отметить, что при выборках вместо идентификаторов tag и process может быть передано "ничего", что будет синонимично "выбору всех" или "для любого". То есть при регистрации расширения для {0, process1} вы добавите всем объектам обрабатываемое в process1 расширение, а при {tag1, 0} всем помеченным как tag1 расширение, обрабатываемое в параллельно с чем угодно, при этом вы берёте на себя ответственность за синхронизацию.

2) Использование
Будем считать что описанное в предыдущей части усвоено и душа требует примеров использования. Несмотря на моё желание о реализации поговорить позже, кое-что всё таки придётся оговорить. Раз языком реализации выбран С++, то давайте его просто надстроим где-нужно. Для начала изобретём синтаксис.
Посылка сообщения: @object->messageName(p1, p2); или @select(tag1, tag2, ..., tagN)->messageName(p1, p2); (параметров сколько нужно). Первый вариант - посылка целевому объекту, второй - всем объектам, одновременно помеченных перечисленными тегами. Возможно будет посылка сообщения произвольному списку объектов. Возможно будет @map. Теги добавляются так: @object += tagName; убираются: @object -= tagName; Можно вместо одного тега указывать список тегов: @object [+/-]= (tag1, tag2) Все вышеприведённые конструкции возвращают значение, какое именно, опишу в деталях реализации.

Расширение описывается так:

@extension(tagname, processname) {
//тут как обычная C++ структура
// в частности можно сделать конструктор и деструктор =)
};

@property instanceVariableName; // сгенерирует set/getInstanceVariableName
@receive T messageName(p1_t p1, p2_t p2) {
// implementation
@this->message1(p1); // куда же без self =)s
} // от определения функции отличается только словом @receive, и тем, что идентификатор - это произвольная строка до первой открывающей скобки.
...
@end(GlobalData) //@extesion(...)
/* GlobalData - указатель на void, через который можно передать нечто, доступное ( DataType *gd = (DataType *)@global; *gd->field; ) внутри любого экземпляра расширения.
Если ничего передавать не надо - можно писать просто @end*/

Приведённое транслируется в человекопонятный С++, точнее в обращения к системе сообщений и хэши с комментариями ( <большая страшная чиселка>/*original text*/ ). Всё абсолютно можно будет делать/звать и ручками. Каждое расширение может быть определено не более одного раза. Если в run-time нужно добавить функцию (например при импорте оной из скрипта), то сделать можно будет только обращением к системе сообщений. Расширения объявляются прямо в .cpp, .h не нужен, ибо для компилятора имена сообщений это просто хэши. Объекты плавают где-то в эфире, а мы оперируем лишь ссылками на них, в отместку они считают ссылки. В С++ вероятно умными указателями, при обращении из скрипта вероятно привязками, в самом низу - функциями системы. Будет интроспекция средствами вызовов соотв. подсистемы.
Поскольку оно лишь надстройка, то вполне допустимо писать С++ код рядом или совсем вперемешку. Есть 3 способа использования надстройки: 1) только как обёртку вокруг чего-то завершенного на С++, 2) писать всё, что уровнем выше только в таком стиле и 3) разумненько смешивать в примерно равных пропорциях. Ожидаете рекомендацию? - Её не будет! =)
Пожалуй пришло время какого-нибудь примера =)

@extension(mouse, logic) {
int x, y;
};
@property x;
@property y;
@property x,y; // так тоже можно =) но сгенерирует только set-функцию
@end

...

// где-то:
int x, y;

getMousePos(&x, &y);
@select(mouse)->setXY(x, y);

...
Object cursor; // это всего лишь ссылка на объект
@cursor += (mouse, renderable, mainCursor);
@cursor->setRenderType(SPRITE);
@cursor->setTexture("justCursor");
@cursor->setSizeXY(10, 10); // возможно будет синтаксис для посылки сразу многих сообщений
// Всё, мы сделали курсор.
...
// где-то ещё
Object cursor = @select(mainCursor).first;
@cursor += physicalObject;
@cursor->setMass(10);
@cursor->setShape(@physics->shapeBuilder().sphere(10)); // shapeBuilder() сообщение, которое возвращает обычный С++ объект, а так же типичный пример сообщения, которое не привязано к процессу
// После 4х строчек кода курсор становится способен к интеракции с игровым миром.



Ещё благодаря интроспекции оно замечательно экспортируется в скрипт, и замечательно сериализуется.

Теперь немного об идиомах которые я (кое где Илья поучаствовал) успел придумать. Во-первых в приведённом выше примере, можно было видеть присвоение уникального тега для однозначной идентификации, вместо того, чтобы как-то явно передавать ссылку на объект. Во-вторых у нас могут быть несколько ситуаций доступа к большим данным:
1) Данные большие, но нужны только в одном расширении. Очевидно не требует никаких доп. усилий
2) Данные нужны разным тегам/процессам/расширениям, но маленькими частями - сделать объект контролирующий данные.
3) Данные нужны всем и целиком - взять ответственность за синхронизацию на себя и добавить сообщение для прямого доступа.
В-третьих сервис теперь делается до невероятного просто:

@extension(cookieBringer, cookies) {
unsigned cookiesRemains;
@datum_constructor(): cookiesRemains(100) {}
};
@receive Cookie bringTheCookie!(void) {
return cookiesRemains > 0 ? --cookieRemains, Cookie() : NotfreshCookie();
}
@end
...
Object cookieBringer;
@cookieBringer += (cookieBringer, service);
...
@select(cookieBringer, service).first->bringTheCookie!();


Недораскрытой осталась тема использования для нашей задачи. Тут всё вроде бы просто. Система добавляет системные расширения и сервисы. Потом грузит поддвижки, которые просто С++ классы, те, в свою очередь, регистрируют необходимые расширения и создают свои сервисы. И понеслась. Про экспорт в скрипты и импорт оттуда я уже писал. Про потенциально возможные "толстые" данные тоже.

На данный момент вроде бы всё.

Реализация
Реализация состоит из нескольких частей: 1) microthreading framework, 2) object communication system, 3) syntatic sugar. Всех борцов с кодогеном прошу обратить внимание, на то, что собственно кодоген только в пункте 3) и это лишь сахар. Рассмотрим всё по порядку.
MTF вещица заслуживающая отдельного рассмотрения. Потому её здесь опишу лишь вкраце. Есть фиксированное количество потоков (пул потоков). У каждого потока один "вход" и один "выход". На вход подаётся очередное задание (одно). На выходе - очередь заданий, запрошенных потоком на выполнение. Задания помечены. Задания с одинаковой пометкой одновременно не выполняются. Есть "почтальон", он забирает из очереди каждого потока задания и передаёт дальше. А дальше система фильтров, в которую можно добавлять кастомные. Таким образом можно вмешаться в процесс диспетчеризации. Отфильтрованные задачи раздаёт освободившимся потокам планировщик. Как можно видеть, у каждой упомянутой очереди только один читатель и один писатель. Что позволяет реализовывать вставку при многих потоках с константной сложностью (если предварительно выделить память).
Поверх MTF выстраивается система взаимодействия объектов. Примерно так:

// Datatypes
typedef int32_t Idn;
typedef Idn Tag;
typedef Idn Process;
typedef Idn Object;
typedef Idn Message
typedef void * Data;
typedef ??? List; // list of Idns

typedef (void *) (*IMPL)(Object this, Data data, ...);
// Message sending
void *sendMessage(Object, Message, ...); // сложность примерно константа, если никто в момент вызова не регистрирует новый тип сообщения.

// могут пригодится при ручной оптимизации, так как, они будут просто возвращать скаляр, при условии если обработчик возвращает скаляр. Иначе падают по ассерту.
long sendMessage_l(/* ... */);
long long sendMessage_ll(/* ... */);
double sendMessage_d(/* ... */);
long double sendMessage_ld(/* ... */);

void sendMessage_(Object, Message, ...); // если нам глубоко плевать на результат. Самая быстрая в семействе =)
void sendMessage_list(List, Message, ...); // не возвращает ничего. Так всё таки будет сильно проще.

// Object manipulation
Object newObject(void);
void retainObject(Object);
void releaseObject(Object);

void addTag(Object, Tag);
void removeTag(Object, Tag);
void addTags(Object, List);
void removeTags(Object, List);

List selectObjects(Tag, ...) // list of tags must be null-terminated. If first param is 0, then return all objects.

// Extensions
int bindFunctionToExtension(Tag, Process, Message, IMPL, const char *argtypes); // argtypes - строка описывающая типы в сигнатуре
int bindDataToExtension(Tag, Process, void * data, size_t);

// Introspection
const char *getNameByIdn(Idn);
Idn getIdnByName(const char *name);
List getObjectTags(Object);
List getObjectMessages(Object);

Под капотом таблица тегов, таблица сообщений, таблица объектов. Таблицы при чтении не блокируются, если никто не пишет. Пищут только bind* фунции. Потому при ручной регистрации надо думать, когда её звать. Для С++ Object ещё сверху оборачивается в умные указатели, которые за нас ссылки посчитают. Где GlobalData? фтопку её, ибо можно через структуру передать.

Вот мы и подобрались к самому щекотливому вопросу =) Кодогенерация нужна для двух вещей - чтобы строить хэши (Idn) по строке в compile-time (не представляю как это можно на шаблонах, они со строками вроде совсем никак) и чтобы не мучить себя ручной регистрацией всего. Прописывать полный синтаксис до утверждения мне как-то страшно =) Ничего, чего бы я не показал в примерах из 2й части не будет.

@object->messageName(p1, p2);
sendMessage(object->value, /*messageName*/ p1, p2); // Выбор происходить будет только из 2 вариантов void *, void. На основе положения в тексте. Т.е. void будет выбран если ничего слева кроме табуляци нет, а предыдущая строка кончается ';' или '{' (+ ещё немножко случаев =) )

@select(tag1, tag2)->messageName(p1, p2);
sendMessage(selectObjects(/*tag1*/, /*tag2*/), /*messageName*/ p1, p2);

@object [+-]= ...
// Думаю очевидно =)

@extension(tag, process) {
// just structure defenition
@data_constructor(..)...
@data_destructor(..)...
}
@receive Type messageName(P1 p1, P1 p2) {
// ...
}
@end

struct STRUCT__tag__process {
// just structure defenition
STRUCT__tag__process(..)...
~STRUCT__tag__process(..)...
};

Type messageName_impl_tag_process(Object _thisobj, STRUCT__tag__process *data, P1 p1, P2 p1) {
// ...
}

@this
CppObject(_thisobj)
@data
data

// Да, типы переменных надо будет подсмотреть в структуре. Но фича не обязательная.
// Но как можно видеть экономит время
@property x;
@receive void setX(int aX) {
@data->x = aX;
}
@receive int getX(void) {
return @data->x;
}

@property y, z;
@receive void setYZ(int aY, int aZ) {
@data->y = aY;
@data->z = aZ;
}

Ещё тривиально генерируется отдельный .cpp файл, с вызовами функций регистрации, который потом надо будет просто инклуднуть.
Вроде бы даже всё =)

Ничего кроме пользы от такой генерации не вижу. Это нормальная практика у многих - перекладывать рутину на машину. Плюс шаблоны всё таки медленнее компилируются.
До скрипта оно пробрасывается замечательно как раз за счёт интроспекции и возможности прямого вызова соотв. функций. Что ещё приятнее, написанное в скрипте может расширять объект, это как раз позволяет писать некритичные по скорости подсистемы целиком на скрипте.

3) Резюме
Итак мы имеем масштабируемую систему (как на 2-8 так и на 20-80 ядер). Имеем удобную модель для описания взаимодействия внутриигровых сущностей. Имеем возможность быстро писать хорошо абстрагированный быстрый код и возможность незаметно интегрировать его с ещё более быстро написанным, хорошо абстрагированным, но менее быстрым кодом.
Из минусов лично я вижу только относительную небезопасность прямых вызовов руками. Но это издержки C/С++ Как раз это решается (если очень надо) обвешиванием шаблонами.

Маленькие Открытия

Музыка
Благодаря Last.fm нашел и подсел:

  1. Diablo Swing Orchestra
  2. Akphaezya
  3. Unexpect
Это всё авангардные металлы.

Посмотрел

  1. Зелёная Миля - отличненько.
  2. Avatar 3D - сюжет банален, но потрясающе сделанный мир. Оно того стоит.
  3. Досмотрел таки второй сезон Code Geass. No comments ;)
  4. Aoi Bungaku Series - аниме-экранизация японской классики. Очень хорошо. А на фоне, того шлака рядом с которым оно вышло, просто гениально =)
  5. Мой сосед тотторо - Миядзаки, этим всё сказано
  6. Какое-то блядски тупое гаремное онеме  про Красную ночь, человеокв - кусков и парня с золотым убер-глазом-стохастическим-анализатором. Пожалел потраченнное время.
  7. Человек Дождя - ох не зря оно в топе на кинопоиске.
  8. Властелин колец реж.версия. - круто, но вконец затянуто.
  9. Все умрут, а я останусь - быдло такое быдло. хуже треша лучше нет. шикарно.
  10. Школолололололо - посмотрел несколько серий. Примечательно то, что все до одного герои не вызывают ничего кроме отвращения, неподдельного. Камера а-ля "снмало школие на мобилу" решение сомнительное. Вначале казалось, что смотреть можно, потом осознал, что нельзя. Плохо, что во многом показанное правда (не во всех школах разумеется) .
  11. Черная летающая хуйня  - черная летающая хуйня. Русский спидер-мен бессмысленный и беспощадный.
 Играл

  1. Dead space - забавно поначалу, не осилил до конца, бо наскучило
  2. Overlord 2 - поначалу гениально (в первую часть не играл), потом заскучал.
  3. Thief 3 - запоем за 3-4 суток.
  4. Mass Effect - запоем за двое суток. Наверное хотел бы ещё разок. Подумываю о второй части.
    Что, характерно, там есть солнечная система, в которой всё названо в честь славянских богов
Софт
  1. Kupfer - аналог quicksilver. Много удобнее обычного окна выскакивающего по alt-f2
  2. Google Chrome - очень быстрый, годый броузер от сами знаете кого. Правда пока есть глюки. Но огнелис опасносте. 
  3. Redmine - вкусненький СУПчик