SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

46
Метаданные и автогенерация кода. SECON 2014. Щеваев Павел, @pachash

description

Надежный обмен данными в гетерогенной среде, между разными платформами и технологиями является одним из ключевых моментов разработки сложных систем. Во время обмена данные преобразуются в некоторый промежуточный формат совместимый между платформами. Преобразование в подобный формат и из него — крайне рутинная и подверженная ошибкам работа. Метаописание данных неким декларативным языком с последующей автогенерацией типизированных структур облегчает жизнь разработчику. Снимает с него необходимость задумываться о промежуточном формате, о правильном порядке полей, а типизированность гарантирует выявление ошибок еще на этапе компиляции кода. В докладе будет рассмотрено несколько подобных решений, их плюсы и минусы. Также будет рассмотрен с практической стороны наш собственный формат метаданных, используемый нами на протяжении более 5 лет. Целевая аудитория: Ограничений нет, но в большей степени разработчики, имеющие уже определенный опыт разработки разнородных систем или приступающие к подобной задаче.

Transcript of SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Page 1: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Метаданные и автогенерация кода.

SECON 2014.Щеваев Павел, @pachash

Page 2: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Предыстория

Page 3: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Ye olde year of 2007

Page 4: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

zveriki.com

■ Амбициозная и успешная MMO 3D игра с целым зоопарком технологий:○ Сервер: Linux, C++, MySQL, ассеты и конфиги в

файловой системе○ Клиент: Shockwave Director (Lingo, JavaScript)○ Портал: PHP

Page 5: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Клиент (Lingo, JavaScript)

Сервер (C++)

Портал (PHP)БД (MySQL)

Ассеты, конфиги

Информационные потоки

Page 6: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Пример сущности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;};

Page 7: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Загрузка конфигов

<pet> <breed>bulldog</breed> <max_health>100</max_health> <anim_ids>2,4,6,7,8</anim_ids></pet>

Данные ассоциативны: key => value

Page 8: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Загрузка конфигов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;}

Page 9: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Сохранение и загрузка в БД

id conf_id name x y health

10 128 K-9 128 126 5

20 165 Polkan 764 356 9

... ...

Данные ассоциативны: key => value

Page 10: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Сохранение и загрузка в БД

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; ...}

Page 11: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Передача по сети■ Максимально компакное представление ■ Набор байтов■ Данные представлены последовательно

id conf_id

health x y

name (size)

name

== 1 byte

Page 12: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Передача по сети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; ...}

Page 13: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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) { … } );

Page 14: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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); ... }}

Page 15: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Чот не прикольно

Page 16: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Проблемы

■ Отсутствие проверки структуры данных на этапе компиляции для ассоциативных данных

■ Строгий порядок следования инструкций в сетевом протоколе

■ Громоздкая поддержка обработки ошибок■ Дублирование кода для различных

платформ■ Добавление новых RPC вызывов неудобно■ etc, etc, etc

Page 17: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Решение, изменившее мою жизнь

Page 18: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода
Page 19: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Пути решения

■ Найти готовое решение○ Из всего глянулся Thrift (http://thrift.apache.org)○ … но это была середина 2007 года: “никакая”

документация, запутанный код, ориентация только на RPC и проч. и проч.

■ Придумать “нечто” свое

Page 20: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Придумать “нечто” свое

Page 21: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen

Page 22: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen

■ Простой декларативный мета формат■ Поддержка ассоциативных и

последовательных источников данных■ Новые форматы данных просто добавить■ Парсинг и кодогенерация написаны на

PHP: относительно просто расширить■ RPC, сохранение/загрузка в/из БД,

файловой системы■ C++, AS3, PHP, JavaScript, Go (активно

пилится)

Page 23: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

■ Максимальное исключение “человеческого фактора”

■ Возможность быстро пусть и “грязно” расширить для решения частной проблемы

metagen

Page 24: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen: как это работает

■ Парсинг декларативного описания■ Формирование промежуточной структуры■ Кодонегерация под разные “таргеты” из

промежуточной структуры

struct DataPet id : uint32 conf_id : uint32 x : float y : floatend

<?phpclass mtgMetaStruct {}

class mtgMetaField {}

...

PHP

C++

AS3 Go

Page 25: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen: простые типы и структуры

struct ConfPoint x : float y : floatend

struct ConfShapeBase points : ConfPoint[]end

struct ConfShape extends ConfShapeBase id : uint32 name : stringend

Page 26: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen: поддержка enum

enum EnumShape UNDEFINED = 0 CIRCLE = 1 SQUARE = 2end

struct DataShape type : EnumShape ...end

Page 27: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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

■ Структурные токены■ Токены на отдельные поля

Page 28: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen : RPC

RPC 101 MATH_CALC( op : OpType x : float y : float) error : uint32 answers : float[]end

Page 29: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen: приятные мелочи

#комментарии поддерживаются

#...как и поддерживаются некоторые #другие препроцессорные директивы, например

#include shared.meta

Page 30: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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

Page 31: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen: результат генерации

struct ConfPoint x : float y : floatend

Page 32: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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;};

Page 33: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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;}

Page 34: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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());}

Page 35: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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;};

Page 36: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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;};

Page 37: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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") { ...

Page 38: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen : использованиеenum EnumBreed UNKNOWN = 0 BULLDOG = 1 SHEPHERD = 2end

struct PetConfig breed : EnumBreed max_health : uint16 anim_ids : uint32[]end

Page 39: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen : использованиеstruct PetData conf_id : uint32 id : uint32 health : uint16 name : string x : float y : floatend

Page 40: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen : использованиеstruct Pet { PetData data; const PetConfig* config;};

Page 41: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen: RPCRPC 10 MOVE_PET( pet_id : uint32 x : float y : float) error : uint32end

Page 42: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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);

Page 43: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

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); } ...}

Page 44: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

metagen: минусы

■ Требуется настоящий лексер, а не regex парсинг декларативного описания

■ Местами “стихийная” реализация в кодогенерации

■ Пока нет поддержки по-настоящему опциональных полей (data.x.set(10); if(data.x.exists()) { … } )

■ Не является универсальным решением, решает исключительно наши потребности

■ Сlosed source ;)

Page 45: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Альтернативы

■ Thrift (thrift.apache.org)■ Protocol Buffers (code.google.

com/p/protobuf)■ Avro (avro.apache.org)■ Roll your own! :)

Page 46: SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода

Вопросы?