войти зарегистрироваться

Используем списки доступа в шаблонизаторе Smarty

Предыстория



Долгое время мне приходилось сопровождать самописную систему на PHP, которая была спроектирована очень и очень плохо. То и дело приходилось встречать вот такие куски в шаблонах smarty:

{if $user->role eq 'admin'}
{* покажем этот столбик только админам (заголовок) *}
<tr><td>Секрет</td></tr>
{/if}

{if $user->role eq 'admin'}
{* покажем этот столбик только админам (данные) *}
<tr><td>{$row->secret_column}</td></tr>
{/if}


Поначалу никаких проблем с этим не было, хотя немного коробило от мысли о том, что если условия отображения станут сложнее?.. А если роль переименуют? А если нужно предоставить все возможности и для роли (группы), и для конкретного пользователя?

Проблемы же начались, когда руководитель попросил ввести роль poweruser2, которая будет такой же, как роль poweruser, но на страничке salary.php, они будут видеть разные данные. Предстояло перелопатить весь проект и сделать следующие изменения

{if $user->role eq 'poweruser'}
{* only for poweruser *}
{/if}
 
заменить на 
 
{if $user->role eq 'poweruser' or $user->role eq 'poweruser2'}
{* only for poweruser AND poweruser2 *}
{/if}
 


Руководитель клятвенно пообещал, что это единственное такое изменение, поэтому вооружившись grep-ом, необходимые изменения были сделаны. После проведения этой процедуры в пятый раз (каждый раз должен был быть последним) стало очевидно, что необходимо иное решение.

Суть проблемы


Суть проблемы очень проста — незачем смешивать представление и систему управления доступом.

Проблема решена была в два приёма:
  1. Надстройка над убогой системой прав, которая позволяла разделить списки доступа и представление
  2. Предоставление возможности использовать в шаблонах smarty эту надстройку


ACL


ACL — это отдельная история, поэтому для простоты изложения, я упущу детали, и скажу, что для реализации списков доступа добавлялись функции, наиболее используемая имела следующий вид.

/**
* Определяет, разрешен ли доступ к указанной сущности 
*
* @param token идентификатор сущности, доступ к которой проверяется
* @param user имя пользователя
* @param group пользовательская группа
*
* @return true, если разрешено, false, если запрещено
*/

function acl_allowed($token, $user, $group=null) {
    if (!is_null($user) && acl_user_allowed($token, $user))
        return true;
    if (!is_null($group) && acl_group_allowed($token, $group))
        return true;
    return false;
}


Smarty


Изначально планировалось такое использование

PHP:
$smarty->assign('acl_allow_secret_column', acl_allowed('salary.secret_column', $user->login, $user->role);
 


Smarty:
{if $acl_allow_secret_column }
{* show the column *}
{/if}
 


Но после появления километровых простынь вроде

$smarty->assign(array(
    'acl_allow_secret_column' => acl_allowed('salary.secret_column', $user->login, $user->role),
    'acl_allow_this' => acl_allowed('salary.this', $user->login, $user->role),
    'acl_allow_that' => acl_allowed('salary.that', $user->login, $user->role),
    'acl_allow_total' => acl_allowed('salary.total', $user->login, $user->role),
    'acl_allow_god_mode' => acl_allowed('salary.god_mode', $user->login, $user->role)
));
 
 

были выявлены недостатки такого подхода, которые заключались в том, что надо постоянно отслеживать, что все необходимые acl-ки доступны в шаблоне, а также неплохо было бы удалять те, которые стали ненужными в ходе эволюции проекта.

Окончательное решение


Была создана smarty-функция acl_allowed

PHP:

function smarty_function_acl_allowed($params, &$smarty) {
    if (! isset($params['token'])) {
        $smarty->trigger_error('Token parameter is mandatory');
    }
    $assign = isset($params['assign']) ? $params['assign'] : 'allowed';
    $allowed = acl_allowed($params['token'], $params['login'], $params['group']);
    $smarty->assign($assign, $allowed);
}
 
function smarty_add_functions(&$smarty) {
  /* ... */
  if (is_object($smarty))
    $smarty->register_function('acl_allowed', 'smarty_function_acl_allowed');
  /* ... */
}
 


А код преобразился так

{* Было *}
<table>
<tr>
  <td>Name</td>
  <td>Position</td>
  {if $user->role eq 'poweruser' or $user->login eq 'boss' or $user->login eq 'vasilisa'}
  <td>Salary</td>
  {/if}
</tr>
{foreach from=$employee_list item='employee'}
<tr>
  <td>{$employee->name}</td>
  <td>{$employee->position}</td>
  {if $user->role eq 'poweruser' or $user->login eq 'boss' or $user->login eq 'vasilisa'}
  <td>{$employee->salary}</td>
  {/if}
</tr>
{/foreach}
</table>
 
{* Стало *}
{acl_allowed token="salary.show_salary" login=$user->login group=$user->role assign="show_salary"}
 
<table>
<tr>
  <td>Name</td>
  <td>Position</td>
  {if $show_salary}
  <td>Salary</td>
  {/if}
</tr>
{foreach from=$employee_list item='employee'}
<tr>
  <td>{$employee->name}</td>
  <td>{$employee->position}</td>
  {if $show_salary}
  <td>{$employee->salary}</td>
  {/if}
</tr>
{/foreach}
</table>
 
 


После этих изменений нужда править smarty-шаблоны при найме нового сотрудника или увольнении/переводе старого, а так же реформировании отделов и перераспределении между ними обязанностей свелась к минимуму, а детальное распределении прав стало возможным вынести в админку.
  • php, smarty, acl

Автомойка. Поднимаем через Яндекс Директ?

Чем может помочь интернет автомойке на улице с низким трафиком, которая еле-еле сводит концы с концами?

А если собрать клиентов через Яндекс Директ?
Задание для рекламщика не интересное, не креативное.
Итак.
Что седлали:
Шаг 1. Проанализировали потребителя — кто может быть потребителем услуг, как далеко готов ехать клиент ради мойки или других услуг, как сделать клиента постоянным.
Шаг 2 Проанализировали окружение — сколько конкурентов вокруг, какие они оказывают услуги, какова их стоимость, какова их загрузка, сильные и слабые стороны, опыт конкурентов удачный и не очень, конкурентные преимущества.
Шаг 3 Приступили к написанию кампании в Яндекс Директе.

Пишем первое слово: автомойка. Те кто пишет ключевые слова для компаний в Яндекс Директе, знают, что после первого слова на лист проливается целый поток слов, которые по мнению автора могут быть запросом. Потом поток становиться слабее, слабее, слова уже не выливаются, а выдавливаются, притягиваются «за уши», вымучиваются и наконец наступает ступор – слов нет. Через какое-то время на ум приходит что-то еще и еще. В конце-концов мы имеем базовый набор ключевых слов.
Далее берем каждое слово из базового списка и включаем фантазию. Думаем, как потребитель может их написать (используя возможные опечатки, ошибки, сленг и т.д.) – тут можете сами попробовать. Сколько вариантов написания известной марки Hyundai на двух языках Вы сможете предложить? У меня набралось 37. Про йогурт молчу.
Далее начинаем работу с минус словами:
Первый уровень – не целевые запросы. В нашем случае мы продаем услугу и соответственно, нам не нужны запросы, связанные с технологией процесса: «Химия для мойки, оборудование для мойки, аренда мойки, организация мойки, и т.п.». Вычитаем слова автомойка –Керхер. Тоже поле для фантазии. Сколько вариантов написания этой торговой марки может использовать потребитель?
Второй уровень минус слов это геотаргетинг. В Яндекс Директе Вы даете объявление на всю Москву, а ведь степень локализации гораздо выше и тут вы начинаете минусовать географические названия: округов — САО, СВАО, ВАО, районов — Строгино, Кунцево, исторических названий — Замоскворечье, Гагаринский, станций метро — Академическая, Курская, улиц — Профсоюзная, Врашавское, Энтузиастов.
Итак, потребитель для мойки автомобиля обычно выбирает две точки: рядом с домом или рядом с работой. Как показали опросы знакомых и друзей, для мойки критичным является удаленность (не более 10 минут от дома или работы или на пути из дома на работу), цена, качество, удобство и безопасность (особенно для женщин). Но это уже к вопросу о составлении объявления в Яндекс Директе.
Если мойка берется за нижний ценовой сегмент, то добавляем запросы со словами дешево, не дорого. Если Ваш потенциальный клиент в премиум сегменте, то — качественная, VIP.

Автомойка это не только помывка автомобиля, но и химчистка салона, полировка кузова, мойка двигателя – и эти доп услуги иногда составляют до 30% выручки предприятия. Запросы по допуслугам выносим на отдельные объявления. Запрашивает клиент полировку кузова – получает в Яндекс Директе ответ не об автомойке, а именно о полировке кузова.
Результат:
Разработка кампании заняла 2 недели (с корректировками и уточнениями).
Результат для мойки 12 корпоративных договоров за 6 месяцев (все клиенты пришли через Интернет), полное заполнение мойки и 2-3 новых клиента ежедневно на допуслуги – и все это при месячном бюджете на Яндекс Директ — 12 000 руб. в месяц.
  • малый бизнес, яндекс.директ, реклама, рекламная кампания

Анализ: причины триумфального успеха No More Heroes

No More Heroes — игра, буквально-таки перевернувшая всё Wii-community. Игроки уже начали забывать о том, что у них есть Wii, ибо всё самое интересное уже было пройдено, но наконец нашли то, чего так долго ждали: они нашли действительно увлекательную и захватывающую игру.

Но в чём причина столь триумфального успеха NMH? Ответ на этот вопрос я постараюсь найти в этой статье.

Простой сюжет



Да, вот так вот банальненько. В NMH довольно-таки простой сюжет: у парня кончились деньги, ему нечем их заработать, у него есть катана, он идёт убивать. Хотя многие могут подумать: «что за глупость, как банальность может быть плюсом?», на самом деле, это несомненно плюс, ибо простота сюжета позволяет сконцентрироваться на геймплее.

Точно сбалансированная сложность



Сложность игры на уровне Normal сбалансирована идеально. Это значит, что игра не заставит Вас перепроходить один и тот-же уровень по тысяче раз, но в то-же время, не даст Вам времени на отдых! А «хардкорные» игроки, которым настоящее удовольствие приносит лишь та игра, что напрягает их по-максимуму, могут выбрать более сложный режим Hard.

Собирательный образ главного героя



Целевая аудитория игры — Япония и Америка (к сожалению, так уж на Nintendo-сцене сложилось, что про Европу обычно забывают :( ), потому главный герой был проработан именно таким, чтобы казаться игроку знакомым, чтобы он мог вжиться в его роль. Что может лучше проиллюстрировать человека из Америки или Японии, имеющего Wii, чем роль анимешника-отаку, у которого нет денег? Это, конечно, может обидеть некоторых владельцев приставки (приношу извинения от имени своего и разработчиков :) ), но собирательный образ в целом получился очень даже неплохой.

Юмор



В целом, атмосфера игры довольно-таки серьёзна — что может быть серьёзней жизни наемного убийцы? Но при этом, разработчики смогли восхитительнейшим образом разбавить серьёзность истории небольшим количеством юмора — небольшим, но достаточным для того, чтобы игрок не заснул, слушая очередной диалог Трэвиса и Сильвии.

Унитаз в качестве места для сохранения, не понятно зачем присутствующая кошка Трэвиса, с которой можно играть и многое, многое другое — всё это примеры небольших шуточек, которыми разработчики успешно разбавляли густой коктейль атмосферы игры.

Сбалансированное управление



NMH имеет очень хорошо сбалансированное управление. Когда некоторые игры для Wii заставляют нас постоянно беспорядочно махать руками, а другие заставляют задуматься, зачем же ты купил Wii, если играешь, держа контроллёр горизонтально и используя его как простой геймпад, то NMH нельзя отнести ни к одной из групп. Здесь Ваши руки не начнут отваливаться после первых минут игры, ровно как они и не будут в покое слишком долго. Управление в NMH — идеальный баланс между кнопками и акселерометром.

Серьёзная игра на детской консоли



Многие критикуют Wii, называя консоль «детской». До выхода No More Heroes, по большей части, это действительно было так — большинство игр были простенькими казуалками вроде Марио или Зельды, небыло практически ничего «серьёзного», жестокого. Вдоволь наигравшись в Wii Sports, игрокам захотелось крови, им захотелось размазать чью-нибудь голову о ближайшую стену! Дабы избежать кровопролития (и заодно заработать немножечко денег), Grasshopper Manufacture дали им такую возможность, виртуально. Естественно, игроки не могли упустить такой шанс наконец поиграть во что-нибудь «взрослое» на Wii.

Смешение жанров



Ещё одним фактором, сыгравшим далеко не маловажную роль в триумфе NMH является то, что точно назвать жанр игры почти невозможно. Это и экшн, и hack-and-slash, и файтинг, и сборник мини-игр (вспомните работы вроде сбора кокосов и покоса травы). И даже старенькому дедушке Восьмибитовичу нашлось место в этой уютной компании жанров! В общем, GM, разработчики NMH, потрудились на славу, смешивая самые лучшие и интересные качества каждого из жанров.

Ну вот и всё. Вышенаписанное — мое представление о тех факторах, что могли стать ключевыми в популярности NMH. Возможно, я что-то упустил — буду рад, если кто-нибудь укажет мне на это ;)
  • nmh, no more heroes, nintendo, успех, триумф, анализ

Карманный маршрутизатор pfSense на базе BSD

pfSense — это маленький маршрутизатор. Бесплатный, построен на хорошо обработанной напильником FreeBSD. Его можно использовать как маршрутизатор, farewall, VPN-шлюз и т.д. Проект начат в 2004 году. На сегодняшний день Нужен ли вам такой маршрутизатор или проще купить коробушку от D-LINK, Zyxel, LinkSys и т.д. выбирать вам. Решая задачу, для одного из Заказчиков, мне pfSense оказался очень полезен и удобен.

Поехали!
Итак, я решил написать первую статью на хабр. Признаюсь, я давно читаю хабр и желание написать что-нибудь давно во мне засиделось. Не смотря на то, что по роду деятельности, в моих руках бывает множество разных железок самого разного уровня, а также программ и решений с которыми приходится возиться, я все как-то не находил подходящего случая. И вот, кажется такой случай появился…

Я вообще большой любитель таких игрушек. Время, когда нужно было долго возиться причесывая UNIX-подобную систему, даже для мелких задач постепенно уходит (на самом деле, а уходит ли?). Нет, я не говорю о том, что этого совсем не надо будет делать, есть разные задачи, есть разные способы их решения.
В повседневных заботах системного администратора так ли много на это времени?.. Такие милые вещицы как FreeNAS, openfiler, smoothwall и pfSense, они, для меня, как ложка меда в бочке дегтя!

Вам нужно откатать iscsi в ESX/ESXi, но у вас под рукой нет железки? Возьмите openfiler и поставьте его на любой компьютер. Вам нужно сделать NAS сервер из старого компьютера у жадного заказчика или, к примеру, сделать себе хранилку в домашней сети? FreeNAS — то что нужно! И уж конечно все подобные причиндалы работают в виде виртуальных машин и часто уже существуют в виде appliance. (Кстати, забегая вперед, образ vmware pfSense, взятый с сайта, в Vmware server 2.0.2 не заработал. Пришлось инсталлировать.)

Давно искал что-то подобное и именно на BSD системе, хотя таких систем много, но чаше на linux. С Linux я в контрах, его хуже знаю чем BSD. Терять время и заниматься инсталляцией BSD системы с нуля, настройкой firewall, NAT и прочих сервисов для задач тестирования, не сложно, но время терять не хотелось.

Для тестов, мне был нужен самый простой и тупой маршрутизатор между двумя серверами, на которых я поднял VmWare ESXi 4.0 и несколько виртуальных машин. Внутренняя маршрутизация ESX-а, во всех его вариантах, мне не подходила. Никакого, аппаратного маршрутизатора под рукой у меня не было. Готового образа BSD сисемы, да и дистрибутива под рукой тоже не оказалось. Качать? Мм-м-м…

Выход был найден не сразу. Точнее, маленьких, кастомных дистрибутивов с web-мордой для роутеров не так много, а под BSD еще меньше.

Требования к системе — минимальны.
CPU — 100 MHz Pentium (?!? Артефакт!)
RAM — 128 MB

При инсталяции на жесткий диск (мне хватило виртуальных 300мб на диске)
CD-ROM для инсталяции (или используйтея ISO-файл.)
1 Гб на диске (реально меньше)

для Embedded решения
128 MB Compact Flash
Serial port for console

Какую пользу мы имеем с гуся, Шимеле?
  • Firewall (packet filter от OpenBSD;
  • Простой, графический Web-интерфейс для настоек всего что есть;
  • Простая, в одно нажатие инсталляция;
  • Возможность использовать как Wireless Access Point, если у вас есть адаптер;
  • Поддержка нескольких подсетей включая алиасные с firewall (можно легко организовать подсеть для каждого подразделения вашей организации)
  • Traffic Shaping
  • State Table
  • NAT
  • Redundancy (несколько маршрутизаторов pfSense можно объединять, если упал один, второй встает на его место)
  • OpenBSD CARP (протокол избыточности общего адреса)
  • pfsync (синхронизация правил firewall между «коробушками»)
  • Load Balancing для входящего и исходящего интерфейсов.
  • nmap, ping, traceroute в том числе и через web-морду
  • VPN — IPsec, OpenVPN, PPTP (все что надо для VPN!)
  • PPPoE Server
  • RRD Graphs Reporting (графики нагрузки и все такое в реальном времени)
  • Real Time Information (используется AJAX)
  • Dynamic DNS
  • Captive Portal (ээ-э-э?)
  • DHCP Server and Relay
  • Консоль, в том числе и через ssh или RS-232
  • Wake on LAN (pfSense умеет посылать WakeUp на подключенные компьютеры)
  • Proxy Server
  • Встроенный в веб-морду sniffer
  • Сохранение и восстановление правил firewall через веб-морду
  • Текстовый редактор
  • Возможность добавлять любые BSD-пакеты и обновлять систему через веб-морду.


Кому интересно, вот скриншоты

Шкварки
Зачем нужны такие системы? Есть ли им место в наших сетях и системах?
Не знаю. Конечно, городить маршрутизатор для домашней сети из старого компьютера вряд ли кому-то придет в голову. Проще купить коробушку за 1500руб, уметь коробушка конечно меньше, зато электричества кушает меньше и не гудит, да и так ли нужен полный такой функционал дома?

Ставить pfSense жадных экономных заказчиков, которые используют ПК-помойки для маршрутизаторов тоже спорное решение, потому как время простоя иногда бывает больше чем uptime.
Мелкие компании (до 10 сотрудников) скорее всего вполне устроит коробушка-маршрутизатор. А вот в компаниях побольше уже можно попробовать. Во всяком случае, пока, достаточно большое количество системных администраторов предпочитает маршрутизаторы на базе linux & BSD систем, где pfSense вполне уместен. Он прост сам по себе (даже проще чем smoothwall), не требователен к ресурсам, легко ставится, легко администрируется, достаточно производительный.

Продукт достаточно хорошо документирован, есть и Wiki и подробный FAQ. Что касается аппаратной совместимости, то pfSense поддерживает все, что поддерживает FreeBSD 7.2. Да… отлично подходит для тестов, в реальной или виртуальной среде, потому к весит мало и работает сразу.

Чего не хватает?
Не хватает миниатюрной x86 платформы с 500Mhz+ процессором и 512 памяти, с несколькими сетевыми интерфейсами и габаритами как у CD-DVD-привода :). Хотя… у той же супермикры SuperMicro появились надавно сервера на 330-ом Atom-е и i945GC. Малепусенький сервер с 2-мя LAN портами+PCI-E и райзер +4х портовая сетевая карта — уже 6 гигабитных портов за 700-720 доларов в розницу. Да этого достаточно для любых задач фирмы или филиала! И вроде получается дешевле аппаратного роутера, наверное любого вендора при равных технических характеристиках. Это достаточно простое и не дорогое и надежное Embedded решение.
Разработчик утверждает, что их продукт можно поставить на xBox (вот не ясно тока зачем?)

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

П.С. За публикацию и снисхождение к тексту буду очень признателен и благодарен.
Удачи коллеги!
  • маршрутизатор, firewall, Web-интерфейс, freeBSD

Организация Архитектуры Master-Standby в Oracle: Пошаговая инструкция настройки Oracle Data Guard

Являясь постоянным хабрачитателем, давно задумывался над тем, что тема Oracle на Хабре по меньшей мере недостаточно раскрыта. А поскольку тема эта мягко говоря, необьятна :), в качестве «затравки» решил взять достаточно животрепещащую тему — настройку и организацию бесперебойной работы СУБД с помощью архитектуры master-standby.

Или как она называется Oracle Data Guard. Соответственно, недолго раздумывая, статью решил оформить в виде пошаговой инструкции с комментариями и по возможности указанием разных «скользких мест».

Что это такое и Зачем это все нужно



Связка master-standby технологически организует решение, когда сохранность данных и функционирования сервиса в целом достигается решением из двух физических серверов, один из которых является основным, а второй резервный, готовый продолжить обслуживание.

Случаи, когда подобное решение оправдано:
  • СУБД является bussiness-critical участком корпоративной инфраструктуры (другими словами стоимость простоя СУБД сопоставима со стоимостью железа, а стоимость информации и того выше).
  • Размер базы данных занимает многие сотни гигабайт а то и терабайт, что соответственно делает полный разворот базы на новом сервере из полного бекапа делом многих часов.
  • Важное требование IT-инфраструктуры — обеспечение функционирования даже в случае полного физического выхода из строя основного сервера. т.е. такие неприятныя мелочи как внезапно сгоревший raid-контроллер не должны парализовывать работу.
  • Является одним из самых эффективных средств против паранои, нарушения сна и неврозов:)
  • Just for fun


Минусы данного решения:
  • Основной минус один: необходимость еще одной железки — standby сервера:) Хотя, для «тренировочных нужд» всю нижеуказанную процедуру можно провернуть и на одном физическом сервере.


Итак, начнем…
  • high availability, oracle, standby, администрирование, базы данных

К вопросу о кроссбраузерных Data URI

В погоне за оптимизацией сайтов захотел уменьшить количество запросов, не в ущерб размерам оптимизированных файлов.
Цель — передавать в одном файле изображения разных форматов, с разными настройками оптимизации.
Как средство, выбрал data uri и gzip'нутый css файл. Однако IE с data uri работают из рук вон плохо. Но в них есть забавная поделка от мелкомягких — mhtml. Существовавшая реализация не отвечала моим требованиям, т.к. приходилось 1 файл передавать два раза — раз для IE, в mhtml, и второй для всех остальных, в data uri. В поисках решения наткнулся на статью bolk'а, где описывалось решение для формата jpeg и некоторые теоретические выкладки для gif и png. После почти трехнедельного раскуривания манов мне удалось реализовать решение для gif и png и автоматизировать процесс для всех трех форматов.
  • data:uri, css, ie6, web-разработка, png,jpeg,gif,base64, я безумный, mhtml

Мысли вслух о Nintendo

Nintendo — очень интересная компания. Они выдвигают множество уникальных идей, разрабатывают большое количество известных во всём мире игр и консолей. Но всё-таки, работники Nintendo — тоже люди(причём — люди очень специфические), а людям свойственно ошибаться.

Именно об этих ошибках и хотелось бы поговорить в этом посте. Здесь будет представлена исключительно моя точка зрения, если Вы считаете иначе, то уж простите, видимо, наши взгляды на игровые приставки различаются.
  • nintendo, обращение, хомбрю, маркетинг

Путь начинающего программиста

Я начинающий программист и хочу описать поэтапно весь мой путь до сегодняшнего дня. Многие через такое уже проходили, кто то и сейчас проходит, кто то собирается. Надеюсь статья вам понравиться.
  • программирование, новичкам

Какую игру делать?

«Лед тронулся, господа присяжные заседатели!»
Ильф и Петров, «12 стульев»

Разрабатывать игры мы начали в 2008 году. До этого мы занимались созданием сайтов на региональном рынке и понимания того, как создаются игры, у нас не было в принципе. А начали очень просто: после неудачного старта онлайн-казино в конце 2007 года, когда заказчик просто отменил проект, а договора у нас с ним не было, я стал думать, где найти новые проекты. И в этот момент я узнаю, что один из программистов написал простую игру и продал ее за несколько тысяч долларов. «Как так?», — воскликнул я, вспоминая наши бюджеты на сайты, и начал в срочном порядке изучать рынок flash игр.

Зимой 2008 года пришлось распустить команду, так как новых заказов не было и тратить деньги на заработную плату не имело смысла. Но часть команды, поверившая в то, что разработка игр — перспективное направление, осталась (тем не менее, почти все из тех, кто остался тогда, позднее уйдут из команды по различным причинам).

И в это время я начал активно искать контакты людей, готовых приобрести flash игры. Ответ был один: «Сделайте игру и приходите к нам, мы спросим, сколько вы за нее хотите». Ну что же, надо делать. Встал вопрос: «Какую игру делать?». Понятно, что нужно было начинать с небольшой игры и первое, что пришло в голову — тетрис и арканоид.

— Будем делать арканоид.
— Но их же уже много.
— Сделаем 3D арканоид, как Richochet от Reflexive. Такого точно еще никто не делал на Flash.
— Хорошо.


И вот, спустя какое-то время, появилась играбельная версия с одним уровнем и я быстро продал эту игру. Но проект нужно было довести до конца. И тут начались «приключения». В договоре было указано, что нужно тридцать уровней, а у нас был только один. Пришлось срочно сделать редактор уровней, затем каждому придумать по парочке уровней и только после этого все уровни расположить по сложности и выкинуть неинтересные. Это все заняло значительное время, которое я никак не учитывал при первоначальном планировании, ведь при создании сайтов тебе не нужно создавать уровни и заниматься балансом.

Параллельно с работой над этим проектом я начал искать flash программистов и художников, так как веб-дизайнеры совсем не представляли, что и как рисовать, а «старые» программисты ушли в другие конторы, разрабатывающие сайты, не желая учить новый язык программирования и делать «непонятно что и как».

Итогом наших стараний стало то, что в апреле мы выпустили Galaktoid.

Что было правильно сделано:

  • быстро поменяли рынок, на котором работали;
  • не имея возможности нанять людей с опытом работы в игровой индустрии (в нашем городе помимо нас только одна компания, разрабатывающая игры) мы учились всему сами;
  • первыми сделали 3D арканоид на платформе Flash и, что называется, попали в аудиторию;
Какие уроки мы получили:

  • не надо оставлять людей в команде, которые не хотят учиться;
  • всегда надо подписывать договора;
  • при планировании нужно учитывать время на геймдизайн/создание контента;
  • работа с фрилансерами увеличивает время на разработку;
  • разработка игр, первые шаги, мой бизнес, постмортем

О boost::asio::ip::tcp::iostream

Появилась задача написать некий сервер.
Если уж выбор пал на С++, то надо и библиотеку применить, желательно козырную — boost в самый аккурат. Про неё столько пишут и говорят… да еще с таким трепетом и так восторженно, что я не мог не проникнуться идеей приобщиться.

Мысль, что в бусте нет библиотеки для работы с сокетами даже не возникала — она должна там быть. И правда, есть — это boost::asio. Почитав доки я восхитился.
Самое восхитительное, что есть удивительный класс boost::asio::ip::tcp::iostream! Вот его-то и буду юзать в сервере — в чистом сиплюсплюсном стиле потоковый ввод-вывод… Лепота!
Но следует учесть, что boost::asio::ip::tcp::iostream подчиняется законам жанра и по своему интерпретирует «перевод каретки», «новая строка» и т.д. Следовательно бинарные данные пересылать не получится. Для этого был написан класс base64 для кодирования бинарных данных в текстовые. Оверхед траффика в 33% для моей задачи вполне допустим.

Итак, сервер должен обслуживать коннекты клиентов, авторизовать их и что-то делать. Банально.
Сервер должен быть многопоточным. Чтобы на каждый коннект свой тред. Должен быть такой диспетчер-криэйтор — он будет принимать коннекты и создавать треды, которые будут работать с клиентами. Этот диспетчер не должен следить на жизнью тредов — они сами должны разрешать все ситуации. Количество тредов не ограничено — предполагается, что процесс обмена с клиентом будет не столь длителен. А если нет — отключим газ (с) к.ф.
В документации asio есть много отличных примеров. Немного подпилив получим это:

server.cpp
int main(int argc, char** argv)
{
...
boost::asio::io_service io_service;    
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);

    // крутимся  
    for ( ;; ) 
    {
      boost::asio::ip::tcp::iostream* stream = new boost::asio::ip::tcp::iostream;
      acceptor.accept(*stream->rdbuf());  // ждем-c...

      std::cout << "\nClient connected" << std::endl;
        
      // создаем тред job, параметр - указатель на стрим, на котором клиент 
      boost::thread thr(boost::bind(&action::job, stream));
      thr.detach(); // пущай себе летает...
    }

...
return 0;
}

Поток создается динамически, и ответственность за его прибитие лежит на порожденном треде.

Работа с клиентом
thread.cpp
namespace action
{
  void job(boost::asio::ip::tcp::iostream* stream)
  {
    std::string from_remote_side;

    *stream >> from_remote_side;  // ждем от клиента
    if (from_remote_side == "me") // явно упрощенная процедура авторизации ;)
    {
       // здесь остальная реализация протокола
    } 
  }
}


Вот тут — *stream >> from_remote_side; засада. Ввод синхронный, значит тред будет ждать когда что-то там прилетит. А если не прилетит, а если вдруг много коннектов и, естественно, создано много тредов и все они ждут… Ни на какие мысли не наводит? =)
Ограничить время ожидания ввода? Да! Надо вкрутить какой-нибудь таймер, который бы делал что-нибудь полезное — разрывал соединение, например.

Сторожевой таймер
Его задача закрыть поток. Это вызовет завершение операции потокового ввода.
Таймер запускается в отдельном треде из треда, который обслуживает запросы клиента.
Таймер, выждав заданное время, закроет стрим, если job() за время ожидания не установит флаг завершения операции done.
С флагом не все гладко. Рассмотрим ситуацию, когда job() завершится раньше, чем таймер.
По завершении job() адрес, по которому хранится done, станет невалидным, вочдог попытается прочесть данные и access violation.
Объявлять done глобальной нельзя. Так как за неё могут конкурировать другие треды, а этот факт потянет за собой мьютексы и прочие локи… Зачем плодить лишние сущности?
Нужно заставить job() дать возможность таймеру проверить done, т.е. не завершаться раньше сторожевого таймера. А значит нужен какой-то механизм синхронизации. В библиотеке boost::thread есть класс barrier. Он создается с параметром количества процессов, которые должны на каком-то этапе ожидать завершения работы остальных процессов, например, для получения всех необходимых результатов работа всех процессов. Как только все процессы подойдут к точке ожидания, все они разблокируются и начнут работать дальше. В нашем случае процессы просто завершатся.
Класс barrier создается в job(), а watch_dog() получает указатель на него в качестве параметра.

Модифицируем job():
namespace action
{
  void job(boost::asio::ip::tcp::iostream* stream)
  {
    int done = 0;
    int time_out = 30;
    boost::barrier barrier(2);  // два треда должны ждать друг друга
    std::string from_remote_side;
    ...

// запускаем сторожевой таймер на время достаточное, например, для 
// аутентификации клиента (в секундах)
    
    boost::thread thr(boost::bind(watch_dog, time_out, stream, &done, &barrier));

    *stream >> from_remote_side;  // ждем от клиента
    if (from_remote_side == "me") // явно упрощенная процедура авторизации ;)
    {
      done = 1;

       // здесь остальная реализация протокола
    } 

    if ( done ) // если нет, то поток уже закрыт сторожевым таймером
    {
      stream->close();
    }

    delete [] stream; // !!!
    barrier.wait();  // если завершаемся раньше, чем сторожевой таймер, то ждем его
    std::cout << "The end" << std::endl;
  }
}

Cторожевой таймер использует класс deadline_timer из библиотеки boost::asio и класс seconds из boost::date_time.

thread.cpp
namespace action
{
  void watch_dog ( int wait_time, boost::asio::ip::tcp::iostream* stream, 
                   int* done, boost::barrier* barrier )
  {
    boost::asio::io_service io;
    boost::asio::deadline_timer t(io, boost::posix_time::seconds(wait_time));

    t.wait();             // за это время должна завершиться операция
 
    if ( !*done )
    {
      stream->close();   // закрыть поток
      std::cout << "Goof!" << std::endl;
    }

    barrier->wait();         // ждем когда отработает родительский тред
  }
}


Важно: job() и watch_dog() должны быть в одном файле.
  • boost::asio::ip::tcp::iostream, watch dog, timeout, сервер, thread, barrier