SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода
-
Upload
-secon2014 -
Category
Presentations & Public Speaking
-
view
148 -
download
4
description
Transcript of SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода
Метаданные и автогенерация кода.
SECON 2014.Щеваев Павел, @pachash
Предыстория
Ye olde year of 2007
zveriki.com
■ Амбициозная и успешная MMO 3D игра с целым зоопарком технологий:○ Сервер: Linux, C++, MySQL, ассеты и конфиги в
файловой системе○ Клиент: Shockwave Director (Lingo, JavaScript)○ Портал: PHP
Клиент (Lingo, JavaScript)
Сервер (C++)
Портал (PHP)БД (MySQL)
Ассеты, конфиги
Информационные потоки
Пример сущностиstruct PetConfig { Breed breed; u16 max_health; vector<u32> anim_ids;};
struct Pet { const PetConfig* config; u32 id; u16 health; string name; float x; float y;};
Загрузка конфигов
<pet> <breed>bulldog</breed> <max_health>100</max_health> <anim_ids>2,4,6,7,8</anim_ids></pet>
Данные ассоциативны: key => value
Загрузка конфиговint PetСonfig::read(const XmlFile& data) { string breed_tmp; if(int err = data.readString("type", breed_tmp)){ return err; if(!Breed::lookupByStr(breed_tmp.c_str(), breed)) return ERR_BAD_ENUM; }
if(int err = data.readU32("max_health", max_health)) return err;
string ids_tmp; if(int err = data.readString("anim_ids", ids_tmp)) return err; if(!explode(",”, ids_tmp, anim_ids)) return ERR_BAD_ARRAY;}
Сохранение и загрузка в БД
id conf_id name x y health
10 128 K-9 128 126 5
20 165 Polkan 764 356 9
... ...
Данные ассоциативны: key => value
Сохранение и загрузка в БД
int Pet::read(const DbReader& data) { if(int err = data.readU32("id", id)) return err; if(int err = data.readString("name", name)) return err; if(int err = data.readFloat("x", x)) return err; ...}
int Pet::write(DbWriter& data) const { if(int err = data.writeU32("id", id)) return err; if(int err = data.writeString("name", name)) return err; if(int err = data.writeFloat("x", x)) return err; ...}
Передача по сети■ Максимально компакное представление ■ Набор байтов■ Данные представлены последовательно
id conf_id
health x y
name (size)
name
== 1 byte
Передача по сетиint Pet::read(const ByteBuffer& data) { if(int err = data.readU32(id)) return err;
if(int err = data.readU32(conf_id)) return err;
if(int err = data.readU16(health)) return err; if(int err = data.readFloat(x)) return err; ...}
int Pet::write(ByteBuffer& data) const { if(int err = data.writeU32(id)) return err;
if(int err = data.writeU32(conf_id)) return err;
if(int err = data.writeU16(health)) return err; ...}
RPC (клиент)
И
ByteBuffer pckt;pckt.writeU16(PACKET_MOVE_PET);pckt.writeU32(pet.id);pckt.writeFloat(pet.x);pckt.writeFloat(pet.y);
net_send_packet(pckt, function(err) { … } );
RPC (сервер)
int handle_rpc(const ByteBuffer& data) {
u16 packet_type; if(int err = data.readU16(packet_type)) return err;
switch(packet_type) { case PACKET_MOVE_PET: return handle_move_pet(data); ... }}
Чот не прикольно
Проблемы
■ Отсутствие проверки структуры данных на этапе компиляции для ассоциативных данных
■ Строгий порядок следования инструкций в сетевом протоколе
■ Громоздкая поддержка обработки ошибок■ Дублирование кода для различных
платформ■ Добавление новых RPC вызывов неудобно■ etc, etc, etc
Решение, изменившее мою жизнь
Пути решения
■ Найти готовое решение○ Из всего глянулся Thrift (http://thrift.apache.org)○ … но это была середина 2007 года: “никакая”
документация, запутанный код, ориентация только на RPC и проч. и проч.
■ Придумать “нечто” свое
Придумать “нечто” свое
metagen
metagen
■ Простой декларативный мета формат■ Поддержка ассоциативных и
последовательных источников данных■ Новые форматы данных просто добавить■ Парсинг и кодогенерация написаны на
PHP: относительно просто расширить■ RPC, сохранение/загрузка в/из БД,
файловой системы■ C++, AS3, PHP, JavaScript, Go (активно
пилится)
■ Максимальное исключение “человеческого фактора”
■ Возможность быстро пусть и “грязно” расширить для решения частной проблемы
metagen
metagen: как это работает
■ Парсинг декларативного описания■ Формирование промежуточной структуры■ Кодонегерация под разные “таргеты” из
промежуточной структуры
struct DataPet id : uint32 conf_id : uint32 x : float y : floatend
<?phpclass mtgMetaStruct {}
class mtgMetaField {}
...
PHP
C++
AS3 Go
metagen: простые типы и структуры
struct ConfPoint x : float y : floatend
struct ConfShapeBase points : ConfPoint[]end
struct ConfShape extends ConfShapeBase id : uint32 name : stringend
metagen: поддержка enum
enum EnumShape UNDEFINED = 0 CIRCLE = 1 SQUARE = 2end
struct DataShape type : EnumShape ...end
metagen: токены
struct DataPet @POD @table:pet @id:id @owner:player_id id : uint32 player_id : uint32 name : string @default:"K-9" @strmax:128 breed : EnumBreed @default:"BULLDOG" health : uint32 @default:10 ...end
■ Структурные токены■ Токены на отдельные поля
metagen : RPC
RPC 101 MATH_CALC( op : OpType x : float y : float) error : uint32 answers : float[]end
metagen: приятные мелочи
#комментарии поддерживаются
#...как и поддерживаются некоторые #другие препроцессорные директивы, например
#include shared.meta
metagen: опциональные поляstruct DataPet @POD @table:pet @id:id @owner:player_id id : uint32 player_id : uint32 health : uint32 ...end
struct DataPet @POD @table:pet @id:id @owner:player_id id : uint32 player_id : uint32 health : uint32 ... #added in version 1.01 age : uint32 @optional @default:0 end
metagen: результат генерации
struct ConfPoint x : float y : floatend
metagen: результат генерации
struct ConfPoint : public MetaBaseStruct { GAME_RTTI_EX(ConfPoint, MetaBaseStruct); enum { CLASS_ID = 230648249, FIELDS_COUNT = 2, };
ConfPoint(Allocator* = NULL); virtual ~ConfPoint();
bool operator==(const ConfPoint&) const;
i32 x; i32 y;
virtual size_t getFieldsCount() const { return FIELDS_COUNT; }
protected: virtual DataError _read(DataReader&); virtual DataError _write(DataWriter&) const;};
metagen: результат генерации
DataError ConfPoint::_read(DataReader& reader) { CHECK_READ(MetaBaseStruct::_read(reader), "Parent 'MetaBaseStruct' read error");
CHECK_READ(reader.readI32("x", x), "x"); CHECK_READ(reader.readI32("y", y), "y");
return DATA_OK;}
DataError ConfPoint::_write(DataWriter& writer) const { CHECK_WRITE(MetaBaseStruct::_write(writer), "Parent 'MetaBaseStruct' write error");
CHECK_WRITE(writer.writeI32("x", x), "x"); CHECK_WRITE(writer.writeI32("y", y), "y");
return DATA_OK;}
metagen: использование#include "autogen/ConfPoint.h"
ConfPoint pt;
{ JSONAssocReader r; r.init("{x: 10, y: 20}"); pt.read(r);}
{ JSONAssocWriter w; pt.write(w); game::string content; w.dump(content); print(content.c_str());}
metagen: расширяемость
class DataReader {public: virtual ~DataReader(){}
virtual DataError readI32(const char*, i32& dest) = 0; virtual DataError readU32(const char*, u32& dest) = 0; virtual DataError readFloat(const char*, float& dest) = 0; virtual DataError readString(const char*, string& dest) = 0; virtual DataError beginArray(const char*, size_t* arr_size) = 0; virtual DataError endArray() = 0;};
metagen: расширяемость
class DataWriter {public: virtual ~DataWriter(){}
virtual DataError writeI32(const char*, const i32 source) = 0; virtual DataError writeU32(const char*, u32 source) = 0; virtual DataError writeFloat(float source) = 0; virtual DataError writeString(const char*, const char* source, size_t len) = 0; virtual DataError beginArray(const char*, size_t size) = 0; virtual DataError endArray() = 0;
virtual void dump(string& content) = 0;};
metagen: расширяемость
function mtg_go_buf_read(mtgMetaInfo $info, $name, $super_type, $type, $buf, array $tokens = array()) { $str = '';
if($super_type == "scalar") { if($type == 'float') $str .= mtg_go_read($tokens, "{$buf}.ReadFloat(&$name, \"$name\")"); else if (strpos($type, "uint") === false) $str .= mtg_go_read($tokens, "{$buf}.ReadI32(&$name, \"$name\")"); else $str .= mtg_go_read($tokens, "{$buf}.ReadU32(&$name, \"$name\")"); $str .= "\n"; } else if($super_type == "string") { $str .= mtg_go_read($tokens, "{$buf}.ReadString($name, \"$name\");"); } else if($super_type == "struct") { $str .= mtg_go_read($tokens, "({$name}.read($buf), \"$name\");"); } else if($super_type == "array") { ...
metagen : использованиеenum EnumBreed UNKNOWN = 0 BULLDOG = 1 SHEPHERD = 2end
struct PetConfig breed : EnumBreed max_health : uint16 anim_ids : uint32[]end
metagen : использованиеstruct PetData conf_id : uint32 id : uint32 health : uint16 name : string x : float y : floatend
metagen : использованиеstruct Pet { PetData data; const PetConfig* config;};
metagen: RPCRPC 10 MOVE_PET( pet_id : uint32 x : float y : float) error : uint32end
metagen: RPC (клиент)static void on_move(RPCError err, RPCArgs args, void* user) { if(err.occured()) { assert(0); return; } RPC_EXTRACT_ARGS(MOVE_PET, args, req, resp);
printf("Result: %u", req->error);}
RPC_NEW(MOVE_PET, req);req->pet_id = 199;req->x = 18;req->y = 97;
rpc_call(req, &on_move);
metagen: RPC (сервер)<?php
class RPC { ... function RPC_MOVE_PET($in, $out) { $pet = find_pet($in->pet_id); if(!$pet) { $out->error = 1; return; } $out->error = 0; $pet->setPos($in->x, $in->y); } ...}
metagen: минусы
■ Требуется настоящий лексер, а не regex парсинг декларативного описания
■ Местами “стихийная” реализация в кодогенерации
■ Пока нет поддержки по-настоящему опциональных полей (data.x.set(10); if(data.x.exists()) { … } )
■ Не является универсальным решением, решает исключительно наши потребности
■ Сlosed source ;)
Альтернативы
■ Thrift (thrift.apache.org)■ Protocol Buffers (code.google.
com/p/protobuf)■ Avro (avro.apache.org)■ Roll your own! :)
Вопросы?