MysticCoder написал:
Я решил применить компонентный подход для юнитов. Если кто не знает, это когда логика класса разделена по своим ролям на другие классы.
Компонетный подход - это когда статически создается множество компонент (базовых функциональных блоков), а потом пользователь комбинирует их в зависимости от задачи в ран-тайме.
Т.е. если ты покупаешь в магазине блендер, а к нему куча всякой всячины (компонент), то комбинируя их можешь получать разные функциональные возможности.
Аналогично здесь. Компонентный подход - статически создаются компоненты (т.е. заранее). И далее ты в динамике собираешь то, что тебе нужно (т.е. в рантайме, по конфигурации, по обстоятельствам).
Реализовывать компоненты не обязательно с помощью классов. Если же хочется это переложить на современный язык ООП и программной архитектуры, то по твоему описанию это скорее паттерн Decorator (классика GoF: Design Patterns, 1994). Этот паттерн более общий, в зависимости от твоего разделения может быть чуть другой паттерн (если делится например только логика, или если ссылка всегда на один объект на всех и т.д.)
Ну, может быть, в твоей вселенной это и так, а в моей вселенной, пусть будет так как я думаю) Если хочешь, могу исходники выложить, глянешь, компонентный у меня подход или некомпонентный. Мне вот кажется - компонентный. Декораторами тут даже и не пахнет вроде, хотя если я правильно понял, любой класс можно обозвать декоратором для TObject и быть довольным.
В общем не суть все это важно, судя по активности, здесь мало кому интересны технические подробности которые я выкладываю, видимо плюшек подавай да красивостей, поэтому посты будут реже и если будет что показать.
MysticCoder написал:
Ну, может быть, в твоей вселенной это и так, а в моей вселенной, пусть будет так как я думаю) Если хочешь, могу исходники выложить, глянешь, компонентный у меня подход или некомпонентный. Мне вот кажется - компонентный. Декораторами тут даже и не пахнет вроде, хотя если я правильно понял, любой класс можно обозвать декоратором для TObject и быть довольным.
Всего лишь попытался раскрыть оригинальную идею компонентности. Которая никак не отрицает твою реализацию компонентности (и не только её, т.е. ты же там не только компонентность реализовал) на классах.
AtomKrieg написал:
Реализовывал небольшую игру через компоненты и пришел к выводу что полиморфизм и наследование идут лесом при использовании компонент.
Лес замечательно живет с компонентами.
Например, у тебя есть класс, а у него контейнер компонент Components, каждый элемент тип Base либо его наследник.
От Base наследуем Draw. От Draw всякие реализации рисования, но у Draw есть метод рисования. (это про наследование)
А теперь запускаем полиморфизм. Для исходного класса можно написать цикл по Components, который смотрит, если элемент принадлежит к Draw, то вызвать метод рисования.
У нас в бластоффе у каждого юнита много составляющих. Поведение, пэинтер, физика и т.д.
Это позволяет собирать юниты очень гибко. Вести он может себя также как другой, а выглядеть совсем иначе и пускать розовый дым с бабочками вместо ядерных выхлопов.
Сделать такую систему, конечно же, намного геморройнее, но вот профит огромный. Гибкость настройки потом будет поражать простой и удобством.
AtomKrieg написал:
Реализовывал небольшую игру через компоненты и пришел к выводу что полиморфизм и наследование идут лесом при использовании компонент.
Лес замечательно живет с компонентами.
Кстати, да. Тоже пилил небольшие пробы, и как раз оные концепции были как нельзя к месту, даже не знаю, как бы реализовывал без них. Разные компоненты конечно вряд ли под это пойдут, т.к. они по своему смыслу различаются, бо атомарны, а вот скрывающийся за абстракцией атома-компонента конкретный реализатор очень хорошо идёт и с наследованием и тем более с полиморфизмом, тем более если этих реализаторов несколько.
MysticCoder написал:судя по активности, здесь мало кому интересны технические подробности которые я выкладываю, видимо плюшек подавай да красивостей, поэтому посты будут реже и если будет что показать.
Зря, мне как раз интересны особенности в реализации, нежели чем непонятные ролики, можно и обсудить, просто не хочется сбивать с намеченного курса, и даже просто посмотреть к чему придведёт такой курс и сравнить со своим.
Код выкладывать - это всегда хорошо, тем более родной паскаль/дельфи, могу свою поделку выложить с моим видением компонентной работы, однако, я перманентно в исследованиях подобных моделей и пока не нашёл и не вник в какую-то из них, чтобы меня она устраивала, и моя тоже меня ещё не устраивает во всём, просто проба пера из далёкого 12 года.
Для кода я предпочитаю Битбакет с Меркуриалом, было бы удобно увидеть Ваш код тоже там, возможно даже повзаимодействовать, но не проблема и гит и/или гитхаб или свн и фордж.
P.S. И да, тактика - очень хорошо ! Давно хотел отписаться в этой теме, но не хотелось марать бумаги простыми словами, а сейчас самое то.
2phomm,
спасибо за комментарии, все таки без интереса извне энтузиазм быстро угасает)
Залил сорцы на bitbucket.org. Комментов там естесственно нет. Ну, тем интереснее будет мне узнать насколько мой код прозрачен и читабелен :)
git clone https://MysticCoder@bitbucket.org/MysticCoder/eve.git
Видео я залил, чтобы можно было увидеть, что вообще уже есть наглядно. Например по нему видно, что сетевая часть в каком то виде есть и функционирует. Запускается отдельно сервак и отдельно клиенты и то, что происходит на экране одного клиента полностью соответствует тому, что происходит на экране другого клиента. Есть один вид оружия, один вид пули, можно стрелять и наносить урон. Ну и модельку анимированную добавил.
Ну теперь немного технических подробностей...
Мои бодания с сетевым кодом вроде как подошли к концу. Самое главное отличие работы с UDP от TCP про которое нигде в интернете не упоминают - это необходимость соблюдать пропускную способность канала. Хотя наверное я ошибаюсь и у TCP тоже такая проблема присутствует, ведь если в сокет напихать полтора гига фильма за секунду при канале в мегабит, то никакие перестраховки TCP в виде переотправок пакетов не спасут, т.к. системные буфера все таки ограничены и не будут хранить в себе полтора гига. Хотя раз буфер ограничен, что то мне подсказывает, что при его переполнении send просто будет возвращать ошибку, то есть все таки полтора гига в секунду не сможем залить. В таком случае у TCP такая проблема отсутствует. Может кто из знающих более подробно разложет такую ситуацию по полочкам?
Так вот, в случае с UDP, при пихании большего объема информации чем пропускная способность канала в сокет пакеты начинают теряться и приходиться городить систему подтверждений для важных пакетов либо искусственное ограничение скорости пихания данных в сокет. У себя я реализовал оба варианта.
Сокет я поместил в отдельный поток, в нем в цикле сокет читает все доступные на данный момент пакеты в буфера, потом отправляет все помещенные в очередь на отправку пакеты. От каллбеков я отказался, теперь главный поток приложения при необходимости вызывает функцию получения очередного прочитанного пакета и обрабатывает его. Таким образом поток сокета только читает и отправляет пакеты в\из список\ка, а главный поток выбирает\помещает в эти списки пакеты. Сделано это с мыслями о хорошей производительности при больших нагрузках.
Для синхронизации заюзал критические секции, обложив ими необходимый минимум по работе со списками пакетов. С большим удивлением, обнаружил для себя такую ситуацию:
var
List : TSomeObjectList; // список с которым работает 2 потока
procedure ThreadOne;
var
SomeObject : TSomeObject;
begin
while true do
begin
SomeObject := List[0]; // берем нулевой обьект из списка
SomeObject.DoSomething; // а здесь он иногда оказывается нелегитимным
end;
end;
procedure ThreadTwo;
begin
while true do
begin
....
List.Add(OtherObject); // этот поток просто иногда добавляет в список новый обьект.
end;
end;
Список по идее только растет, значит нулевой объект всегда должен быть, поэтому и синхронизировать вроде бы не надо. Но почему то иногда вместо него возвращался мусор. Копания в дебрях TList.Add привели к функции ReallocMem, в справке к которой написано, что если памяти памяти свободной по текущему указателю нет, то она может перенести указатель в другое место. Соответственно, в первом потоке при расширении списка во втором потоке иногда возвращалось значение по старой памяти, которое почему то уже было забито левыми данными. Вот, казалось бы простую операцию SomeObject := List[0]; приходится обкладывать критическими секциями.
После того как сделал механизм отправки\приема пакетов настало время как то посылать данные о юнитах. Я дал каждому юниту свой МемориСтрим, в который начал складывать(по умному вроде сериализировать) данные каждой компоненты юнита. Этот стрим уже передавал по сети от сервера к клиенту и обратно. После прохода по каждому классу с кучей NetStream.Write(Field1, SizeOf(Field1)), а также зеркально Read, энтузиазма как то поубавилось. На каждое поле приходилось 2 длинных строки. А уж при мысли о том, что надо будет поддерживать весь этот зоопарк при расширении полей и всяких компонент хотелось забиться в угол и тихо плакать. Но главное - стало возможным играть по сети.
После некоторых тестов на VPS, я пришел к удручающему выводу, что пять локальных клиентов(+10 левых юнитов, в итоге 15 юнитов на карте), уже жрут всю мою хилую пропускную способность инета в 250КБ/с. Была вспомнена мудрая мысль о том, что можно слать только те данные которые реально изменились. При мысли о том, что надо будет как то искать какие данные изменились, а это придется делать на каждое поле отдельную проверку, начинал подергиваться глаз. Через некоторое время, родился простой и элегантный механизм. Каждой компоненте юнита добавил еще один список. Список хранит записи, а точнее классы, задача которых запоминать указатель на конкретное поле родительского класса, создавать второе хранилище этого поля и сравнивать их между собой. Изменилось поле или не изменилось. Сам класс:
T_NetDiffRec = class
pVariable : Pointer; // указатель на поле, которое необходимо контролировать
pOldVariable : Pointer; // второе хранилище, в нем хранится "старое" значение поля
Size : integer; // размер поля
FromClientAgreed : Boolean; // Этим флагом обозначаются поля которые серв может принимать от клиента. Например, Серв не должен принимать от клиента поле Life, но может принять wasd кнопки.
constructor Create(AVariable : Pointer; ASize : integer);
function IsDiffed : boolean; // есть ли изменение поля?
procedure Update; // синхронизировать поле с его хранилищем
end;
В процедуре формирования и разборки сетевого стрима для компоненты я убрал все эти ненавистные NetStream.Write(Field1, SizeOf(Field1)) и Read, заменив их одним циклом по списку изменений. Теперь для того, чтобы сделать какое либо поле "сетевым", т.е. передаваемым между клиентом и сервом, надо всего лишь зарегистрировать эту переменную в конструкторе следующим образом:
Ага, ссылка корявая, ну, понять как её поправить труда не особо надо много.
Я стащил проект, компилирую (пришлось добавить в tga-шный модуль Xproger'a "мод дельфи", а то лазарь ошибки выдавал), на меня смотрит чёрный прямоугольник - полазил, тыкнул параметр командной строки -s тоже результата нет, подебажил малость, не увидел ничего существенного. Нашёл только закоменченый кусок (модуль сцен_апок со строки 390) для инициализации карты - попробовал его, падает листеррор. поставил СимплГенМап для неконсольного режима - при запуске на меня смотрит статичная (и судя по думающему курсору винды, чего-то подвисает ещё, в чёрном прямоугольнике тож также было) картинка, аналогичная видео из темы, но без человечков, только пачка красных и зелёных прямоугольников на сером фоне.
За сим решил пока не продолжать а послушать инструкций от автора. На всякий случай - вин7х32, нвГТ630, лаз 1.2.0, фпц 2.6.2 , линукса нет, чтобы проверить, пока ленно на виртуалке подымать.
О своём - закинул свой проект (поглядел дату создания, оказалось ноябрь 2011 а не 12 даже год), ссыль https://bitbucket.org/phomm-/mapwanderers он конечно убог, но пару минут поиграться в нём можно, и код при желании поглядеть, он небольшой, меньше килостроки. Там инструкция есть как чего , чтобы "побегать".
Если кто не знает, то скачать из раздела Downloads целиком весь репо можно, экзешка приложена, ничего компилить не надо.
Daemon порицать много чего можно :) у каждого из нас. Имхо, лучше чем порицать, продуктивнее было бы форк-небольшой чейндж- пулреквест (или вдруг автор врайтдоступ даст). Если заведу у себя сей проект, может чего и покалякаю даже.
Хотя, учтывая
Ну, тем интереснее будет мне узнать насколько мой код прозрачен и читабелен :)
можно и устно чего изложить, хотя многое будет субъективно, да и толку мало даст, не кидаться же исправлять кодстайл в 260+ кб кода
продуктивнее было бы форк-небольшой чейндж- пулреквест
Да тут не обойтись мелкими патчами, "господь, жги!" :) В целом архитектура несколько неудачная. Именно по части рендера. Если рендер тестовый, то все равно его стоило инкапсулировать где-то единожды, чтобы потом поменять его без особых проблем.
Ну, тем интереснее будет мне узнать насколько мой код прозрачен и читабелен :)
Отсуствие у филдов префикса F, четкого разделения области видимости, отсуствие пропертей и странные укороченные названия не добавляют читаемости и удобства в использовании твоего кода. Это уж точно.
Кстати, а зачем все модули называть с подчеркивания? что за мания такая?
Daemon написал:
Порицать за glut и glBegin/glEnd можно? И в целом за код рисования прямо в юнитах, без абстракций?
Daemon написал: phomm
продуктивнее было бы форк-небольшой чейндж- пулреквест
Да тут не обойтись мелкими патчами, "господь, жги!" :) В целом архитектура несколько неудачная. Именно по части рендера. Если рендер тестовый, то все равно его стоило инкапсулировать где-то единожды, чтобы потом поменять его без особых проблем.
Давай, порицай, излагай поподробнее, что да как тебе не нравится и как бы ты это поменял. А то я нуб и не очень понимаю, что за код рисования прямо в юнитах без абстракций, и как рендер инкапсулировать?
phomm написал:
За сим решил пока не продолжать а послушать инструкций от автора.
Каюсь, в винде я не тестил, тестил только под вайном. Работает сие так:
Верни комментарий в сцен_апок на место.
Запускаешь "Eve -s" - запускается сервер(не создается никакого окна). После запуска сервера надо запустить клиент "Eve 127.0.0.1"(может немного подумает, потом окно из видео с человечком),
под линуксом почему то срабатывало и просто "Eve", что в коде
xTo.sin_addr := sockets.StrToNetAddr(ParamStr(1));
устанавливает адрес коннекта в 0.0.0.0 вроде бы. Под виндой надо явно указать.
Ну и далее управление - wasd + левый клик мышкой. Если wasd не пашет, то переключаешь язык на английский. Ща потестил немного в винде, обнаружил шо какие то подвисания при старте происходят, надо бы этот вопрос изучить. А пока, после старта сервера, возможно клиент понадобится запускать с задержкой.
Darthman написал:
Ну, тем интереснее будет мне узнать насколько мой код прозрачен и читабелен :)
Отсуствие у филдов префикса F, четкого разделения области видимости, отсуствие пропертей и странные укороченные названия не добавляют читаемости и удобства в использовании твоего кода. Это уж точно.
Кстати, а зачем все модули называть с подчеркивания? что за мания такая?
Филды с префиксом F в основном используются как хранилище для проперти. Проперти я не использую, поэтому и на F называть ничего не надо. Не использую просто потому, что у Лазаруса с отладкой пока не все так идеально, он не умеет вызывать функции во время отладки, так что значения проперти прочитать не получится. Да и вспоминается код на работе, где простая строчка "Angle := Angle + delta" в классе корабля, в отладчике в режиме входа в процедуры превращалась в увлекательное путешествие по килобайтам кода, в котором пересчитывался центр тяжести, геометрический центр, координаты всех устройств на корабле, координаты контрола корабля, еще куча чего то. Вот в отладке проперти ясности точно не добавят)
Про области видимости - замечание хорошее, но это все после рефакторинга. Ну а с подчеркивания - чтобы ясно различать где свой, а где левый юнит, да и в юзесы через контрол-пробел добавлять свои юниты удобнее когда они начинаются с подчеркивания. То же самое относится к названиям классов.
ZblCoder написал:
Да, код немного странный. Любовь к символу "_" заметна сразу.
procedure _LoadMaterials;
T_Mesh = class
procedure T_Mesh.Draw;
хоть А ставит в названии входных параметров.
function T_Map.AddUnit(AUnit: TObject; ADoGenerateID: boolean): TObject;
TObject у юнита? Сделал бы кастомный класс юнита, что ли.
так же, нет деструкторов у классов, это БЕЗУМИЕ.
пример:
убивать созданный TList кто будет?
constructor TS_TextureManager.Create;
begin
Textures := TList.Create;
end;
в T_Map.AddUnit, заюзал TObject, потому что там проблема перекрестных ссылок на модули была. По идее Map это всего лишь карта, и игровые юниты должны её юзать, поэтому в interface _apoc_unit, вставил uses _apoc_map. Но карта должна оперировать с юнитами, а в Interface _apoc_map уже не засунешь _apoc_unit. Так что как то так. Про деструкторы - хай так будет, утечки памяти легко исправляются, лучше исправить их одним скопом, чем при добавлении очередного списка в деструктор его паралелльно прописывать. Еще Билл Гейтс сказал, что 64 кб хватит всем)
Darthman написал:
Если до сих пор возникают проблемы перекрестных ссылок, значит что-то пишется до сих пор неверно.
Окай, покажи как правильно?
У меня раньше и карта и юниты были в одном модуле, и такой проблемы не было. Разнес их логику по разным модулям. Появился такой косяк. Утешил себя тем, что он небольшой. Всего то пара процедур.
Раз пошла такая пьянка, то:
1. Общее.
1а глобалки
1б уже упомянутый код рендера в логике
1в немало литералов - в данные или хотя бы в константы
1г довольно немало магии всякой, работы с указателями, записями, массивами, в итоге всякие циклы, хитрая индексация и т.п. при том что списки тоже в ходу.. лучше бы всё на списках и классах с уже упомянутой необходимостью контроля времени жизни
1д упомянутая инкапсуляция - введение её поможет сделать удобнее то, что в предыдущем пункте
1е использование объектов и поинтеров и последующие касты (тоже можно к магии отнести, но выделяю особняком)
1ё именование несколько напряжное
2 Субъективное
2а коробят конечно подчёрки, но видимо автору нормально и привычно..
2б меня напрягает неслежение за регистром идентификаторов, а также за пробелами между идентификаторами и прочим синтаксом, скачет отступ возле : и параметрами/переменными, скачет идентация у блоков
2в размеры методов - сложно конечно судить насколько возможно что-то с ними сделать, но тем не менее, великоваты
По архитектуре надо сперва покопать, а для этого надо бы инструкцию как собрать чтобы бегало как на видео.
P.S. дико повеселил Cooldawn (идентифаер в сцен_арена_шутер), может ещё какую перловочку найду ))
Давай, порицай, излагай поподробнее, что да как тебе не нравится и как бы ты это поменял. А то я нуб и не очень понимаю, что за код рисования прямо в юнитах без абстракций, и как рендер инкапсулировать?
Это сарказм? Если нет, то завтра отпишу, что бы и как я поменял.