Показаны сообщения с ярлыком programming. Показать все сообщения
Показаны сообщения с ярлыком programming. Показать все сообщения

воскресенье, 8 апреля 2012 г.

Memory Managment Framework. Постановка задачи

Давненько ничего не писал и вот решил исправится. Тем более, что на выходных в голову пришла неплохая тема.
Сразу оговорюсь, что в данном опусе будут только мои мысли и разглагольствования, по-сути просто постановка задачи.  Если хватить упорства, то решение задачи я выложу позжее. И еще мой опыт опирается только на разработку игр, поскольку в своей жизни я больше ничего не писал. Возможно у вас все по-другому :).

Одним из слабых (или сильных) сторон C++ является необходимость разработчикам полностью брать в свои руки управление памятью во время работы приложения. Не у всех это удается одинаково хорошо. Отсюда исходят стандартные проблемы: утечки памяти и фрагментация. Как результат не всякое приложение может проработать больше десятка часов без потерь памяти.
Собственно наша игра на данный момент испытывает теже проблемы. Мы потихоньку правим и улучшаем, но возникает вопрос: а нельзя ли чтобы сразу было хорошо? Ведь в мире существует множество движков, библиотек и фреймворков, которые позволяют разработчикам избежать множество проблем. Неужели нет чего-то подобного для управления памятью.
Размышляя на эту тему я попробовал ответить на ряд вопросов:
  1. Что бы дало наличие такой библиотеки
  2. Какие возможные неудобства и ограничения она бы принесла
  3. Какие требования могут быть к такой библиотеке
Пойду по-порядку и постараюсь изложить все что мне пришло в голову.
 
    Возможности, которые я хотел бы видеть в данной библиотеке. 

  • Различные виды аллокаторов памяти. Если вы до сих пор работали с памятью в стиле ПК (т.е. используя стандартные операторы new/delete), то обязательно найдите кого-то, кто работал на консолях и расспросите его. Многие SDK для консолей уже содержат реализации нескольких видов аллокаторов, которые часто работают эффективнее стандартного. 
  • Дальше такая библиотека дала бы больше возможностей для контроля и отладки расхода памяти: создание дампов, проверка ликов, запись логов и возможность получать карту памяти приложения в любой момент времени и т.д. 
  • Намного более жесткий контроль за выделением памяти. Для многих программистов это является хорошей школой, особенно в наш век Java и C#. Слишком многие свысока относятся к гигабайтам свистящим у виска. Такой контроль позволит установить константный объем памяти, которая будет необходима вашему приложению. 

Собственно тут можно напридумывать много чего. Ограничений нет. Ведь вы полностью контролируете работу с памятью.

    Ограничения и неудобства в работе
Конечно в самом идеальном случае при работе с такой библиотекой ничего не поменяется. Но все же из моей практики может возникнуть ряд проблем.

  • Скорее всего придется отказаться от стандартных операторов new/delete, а ново введенные не будут обладать той же лаконичностью и красотой.
  • Возможно придется определять работу с памятью для каждого класса, т.е. переопределять операторы new/delete для каждого класса.
  • Вам понадобится затратить время на обучение всей команды новым правилам работы с памятью. Причем нужно будет донести не только новый синтаксис, но и понимание новых принципов выделения и освобождения памяти. Иначе счастья не будет. 
  • Могут возникнуть проблемы со сторонними библиотеками и фреймворками. Особенно, если у вас нет их исходников.


    Требования к библиотеке
Требования я разделил на две части. Обязательные и прикольные.
Обязательные:
  • Надежность - любыми способами необходимо гарантировать, что функционал библиотеки под любыми нагрузками будет работать только так, как задумано и описано в документации.
  • Скорость - скорость работы для стандартного аллокатора в релизе должна быть как минимум такой же, как и у стандартной реализации операторов new/delete. 
  • Многопоточность - библиотека должна работать из нескольких потоков. На самом деле это очень интересный момент, с кучей своих кейсов и ньюансов. 
  • Низкая точка старта - по умолчанию библиотека должна работать как стандартные операторы new/delete и не требовать сложной настройки. Естественно все инструменты также должны подхватываться сразу, а не после недели танцев с бубнами.
Прикольные:
  • Кроссплатформенность - большая часть кода библиотеки платформонезависима. Но есть несколько точек, где не избежать взаимодействия с конкретной системой: захват памяти у системы и ее освобождение. Также некоторые платформы имеют несколько типов памяти и могут по-разному с ними работать. 
  • Интеграция с популярными IDE - тут опять подключаем фантазию. Можно представить красивые графики в VisualStudio или Eclipse.
 
     Вот собственно почти все к чему пришел. Также в качестве дальнейших, и более практических, шагов записал следующие вопросы:
  • А есть ли уже готовые решения данной задачи (после 15 минут в гугле почти ничего не нашел)?
  • Как работают стандартные реализации операторов new/delete?
  • Какие решения сделаны в рамках различных игровых движков (OGRE, Torque, Unreal, CryEngine и т.д.)?
  • Насколько сложно самому реализовать текую библиотеку и зачем оно мне нужно?
Если можете поделиться информацией буду премного благодарен.
Всем доброй ночи

понедельник, 19 марта 2012 г.

Анализ дня: недостаток perforce, мобильная разработка на Unity

    Всем привет.
    Сегодня снова две темы дня. И более практических, чем обычно.
    Начнем с Perforce. С этом системой я проработал больше 3-х лет и, за все время, особых нареканий у меня не было. Хотя периодически были проблемы. Так сложилось, что последний год я просидел на паре Svn, Mercurial и только месяц назад пришел на проект, где используется Perforce. База на проекте уже огромная - под сотню гигабайт. Файлы самые разные по размерам, от нескольких байт до пары гигабайт. Самое главное, что их много, очень много. Думаю за полмиллиона уже перевалили.
     Оказалось, что Perforce справляется с таким репозитарием очень туго. Тормозит, появляются ошибки. Например файл регистрируется в базе, но на сервер не попадает. И все, кто пытается обновится, получают предупреждение о том, что файл отсутствует.
     На фоне данной проблемы и вылез, как мне кажется, главный недостаток Perforce по сравнению с остальными системами - Perforce требует подключения к серверу и ответа от сервера для выполнения любой команды. Абсолютно любой.
     Во время работы программист наиболее часто выполняет 3 операции: обновление с сервера, коммит на сервер и чекаут файла. Так вот, если у вас тормозит сервер, то вы можете обновляться и коммитить файлы, пусть и со скрипом. Это не такие частые операции, пару раз в день, а у кого-то и реже. Но вот чекаут файла это жизненно необходимая операция, без нее работать чуть хуже чем никак. SVN берет эту заботу на себя и не требует подключения к серверу, Git и Mercurial вообще не требуют сервера, а вот Perforce требует ответа от сервера.
     Итог грустен: невозможность взять файл на чекаут тупо блокирует работу, срывает настрой и вдохновение.

     Вторая тема более жизнерадостная. Как я уже писал, Unity раздает лицензии на мобильные платформы бесплатно. Я воспользовался возможностью и получил свою лицензию. На выходных начал писать аж две программки для Android. Первая это порт игры Smash Aliens (линк на youtube), а вторая - это программа для тренировок, я назвал ее TrainerBoo.
     В данном случае, можно воспользоваться одним из плюсов Unity и погонять оба моих не доделанных приложения через браузер. Вот Aliens, а вот TrainerBoo.
     Собственно меня теперь волнуют два вопроса:

  1. Размер финального приложения - apk файл для Aliens занимает больше 10MB, а для TrainerBoo - больше 6MB (там вообще нет моих ресурсов, только Unity) 
  2. Производительность на Unity

     По ходу развития проектов поделюсь тем как ведет себя Unity.

     Всем доброй ночи. 

суббота, 17 марта 2012 г.

Kata - ежедневная мантра программиста

    Привет всем.
    Сегодня я хотел поговорить об одном приеме настроя на работу, с которым познакомился во время тренинга по ТДД.
    Я снова взялся за преподавание и неделю назад стартовали занятия в группе по программированию на С++. Еще при работе с предыдущими группами я пришел к выводу, что для начинающих программистов очень полезно писать код, поменьше учить разные паттерны и побольше практиковаться. Писать, отлаживать, снова писать. Со стартом группы я задумался над тем какое-же придумать им мучение упражнение, чтобы они писали код каждый день. Вот и вспомнил об упражнении Ката TDD.
    Что такое ката можно глянуть в вики. Вкратце это просто некий набор движений в боевых искусствах. Изучение на базе ката сводится к повторению этих движений многие тысячи раз.
    Так и здесь. Ката TDD - это задание написать StringCalculator. Выполнять его нужно в начале каждого рабочего дня. Затрачивать на ката от 15 до 30 минут, не более. Задание идет итерациями, добавляя все новые возможности в StringCalculator и здесь важно соблюдать эти итерации. Т.е. даже если вы знаете как написать самый общий код на все случаи жизни, все равно необходимо идти по шагам постепенно наращивая функционал и рефакторя код.
     Учить студентов TDD, естественно я не собираюсь, поэтому не буду и требовать написания кода в стиле TDD. Но даже само по себе это упражнение думаю будет очень полезно.
     Для тех же зубров, которые могут выполнить ката за полчаса, и, кому становиться скучно писать каждый день одно и то же, я могу предложить путь развития ката, который использует сам ее автор. Нужно ставить себе ограничители и дополнительные условия на выполнение. Например: написать ее без использования стандартных библиотек, или с использованием специфических библиотек, написать ее на языке, который хочется выучить, написать ее без использования циклов и т.д. и т.п.
     Чтобы было по-честному я вместе со студентами буду выполнять ката каждый день. Сегодня успел за полчаса сделать только до пункта 3.1. Позор на мою голову.
     На этом пожалуй все.
     Всем доброй ночи.

пятница, 23 декабря 2011 г.

Система юнит тестов - логический компилятор вашего приложения

"A unit test is almost always written using a unit-testing framework" Roy Osherove, The Art of Unit Testing.
Всем привет.

Сегодня хотел поговорить о юнит тестах. Я не буду спорить о необходимости тестов в вашем проекте. Данный момент каждый определяет для себя. Кто-то считает, что это отстой, кто-то не может без них писать код, кто-то считает что именно на его проекте и в его команде это не нужно.
Сам я на данный момент нахожусь в стадии: "юнит тесты это круто, нужно и полезно, но сам я их никогда не использовал в реальном проекте и писать не умею".

Тему эту я решил поднять поскольку был на тренинге "TDD в .NET". Тренинг прошел круто, полезно и об этом можно почитать уже в нескольких отчетах. Просто спросите гугл: TDD в .NET на XPDays и он вам все расскажет.

Но что я вынес для себя и почему выбрал именно такой заголовок статьи, а не другой?
За свои скромные несколько лет работы я вынес одну банальную вещь - программисты народ ленивый, менеджерам всегда не хватает времени, поэтому хватит экспериментов, делайте как работает и не ерзайте. Соответственно, если вы хотите ввести какую-то полезную практику у себя на проекте, то в первую очередь позабодтесь об удобных инструментах для работы. Будь-то ревью кода, continuos integration, continous delivery.

Так вот возвращаясь к теме TDD. До тренинга я думал, что писать тесты до кода это сложно, непонятно, скучно. Но как оказалось наука ушла далеко вперед. Наши тренеры (Александр Белецкий @alexbeletsky и Сергей Калинетц @skalinets) показали нам связку из плагинов для студии и фреймворков для юнит тестов, которые делают разработку тестов удобной, быстрой, а их проверку и запуск настолько нативной, насколько для меня уже стала перекомпиляция проекта. Вам не нужно даже сохранять файлы с вашими изменениями в тестах и логике, для того чтобы видеть ошибки и проваленные тесты.
Потом, вечерком переваривая всю полученную информацию я и подумал о наборе юнит тестов, как об еще одном уровне компилятора - компиляторе вашей логики, который работает в фоновом режиме и сразу же проверяет насколько были правильными ваши изменения.

Инструменты следующие, очень советую посмотреть всем кто пишет на C#:
  • xUnit (NUnit) - фреймворки для юнит тестов
  • NSubstitude - фреймворм для создания моков(заглушек, пустышек для тестов)
  • Test Driven.NET - плагин, который позволяет запускать тесты под разными тулзами, и под дебагом в том числе
  • NCrunch - плагин, который в фоне запускает тесты и выдает вам красивый результат в картинках, визуально показывает покрыт ли ваш код тестами
Конечно следующим шагом стоит поднять все темы касательно написания сложных тестов, сложностей их реального применения и самое главное поддержки актуальности ваших тестов. Я этого делать не буду поскольку не имею такого опыта и могу только повторять чужие слова. Но сейчас мой проект использует .NET технологии, я обязательно попробую TDD и мы сможем похоливорить на эти темы в следующих постах.

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

Всем доброй ночи. 

вторник, 6 сентября 2011 г.

Simple Render. Интерфейс IRenderer

Продолжаем рассматривать демо с организацией простенького рендера. В прошлой заметке я рассмотрел интерфейс IEntity и его реализацию. Сегодня у нас очередь интерфейса IRenderer, который в моей схеме представляет view.
Интерфейс IRenderer достаточно прост и содержит всего один важный для нас метод render, метод в котором и будет находиться код по отрисовке той или иной сущности.
class IRenderer
{
public:
virtual ~IRenderer()
{
}
virtual void render()=0;
};
Посмотрим теперь простенькую реализацию данного интерфейса, для отрисовки объектов типа GameEntity, о которых я говорил в прошлой заметке.

class SimpleRenderer2D : public IRenderer
{
private:
GameEntity& m_Entity;
protected:
const GameEntity& getGameEntity()
{
return m_Entity;
}
public:
SimpleRenderer2D(GameEntity& entity);
virtual void render();
};
И соответственно дальше реализация метода render в данном классе:

void SimpleRenderer2D::render()
{
glPushMatrix();
glTranslatef(getGameEntity().getPosition().x, getGameEntity().getPosition().y, 0.0f);
glColor3ub(255,0,0);
glBegin(GL_QUADS);
glVertex2f(0,25);
glVertex2f(0,0);
glVertex2f(25,0);
glVertex2f(25,25);
glEnd();
glPopMatrix();
}
Теперь все выглядит, как мне кажется, просто и наглядно. Класс GameEntity не имеет никакого представления о классе SimpleRenderer2D, а значит мы можем добавлять и другие view для данного типа объектов, использовать их по своему усмотрению и это никак не отражается на нашей модели.

Остается только собрать все эти классы вместе и запустить наше приложение. Этим мы и займемся в следующей заметке.
Всем доброй ночи. 

вторник, 30 августа 2011 г.

Simple render. Интерфейс IEntity

Всем привет 

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

Интерфейс IEntity представляет любую игровую сущность, это базовый класс для любого игрового объекта. Он на самом деле не большой. Как мне кажется на таком уровне нужно только две вещи:
- сохранение и возможность получения уникального идентификатора нашего объекта
- возможность обновления объекта, выполнения в нем какой-то логики

Соответственно интерфейс имеет следующую реализацию 
       class IEntity
       {
              static unsigned long m_EntityIDCounter;
              int m_EntityID;

       public:
              inline int getEntityID()const
              {
                     return m_EntityID;
              }
              
       private:
              inline void setEntityID(int id)
              {
                     m_EntityID = id;
              }

       public:
              IEntity()
              {
                     setEntityID(++m_EntityIDCounter);
              }

              virtual ~IEntity()
              {
              }

              virtual void update() = 0;
       private:
       };
Целочисленное поле m_EntityID содержит уникальный идентификатор объекта, получить который можно через метод getEntityID, а устанавливается он автоматически при создании объекта. Для того чтобы обеспечить уникальной каждого идентификатора я использую статическую переменную m_EntityIDCounter. Думаю промежутка значений, который можно хранить в long нам хватит выше крыши для идентификаторов. 

Ну а для обновления объекта создан абстрактный метод update. На данный момент он не принимает никаких параметров. 

Идем дальше. В примере я создал только один класс наследник - это класс, который представляет двухмерный объект, как статический, так и динамический. Этот объект не содержит логики, но мы потом сможем это добавить, сейчас же это не важно. 
Вот код нового класса:
       class GameEntity : public IEntity
       {
       private:

              glm::vec2 m_Position;
       public:
              inline const glm::vec2& getPosition()const
              {
                     return m_Position;
              }

              inline void setPosition(const glm::vec2& pos)
              {
                     m_Position = pos;
              }

       public:
              GameEntity();

              virtual void update();
       };

Конструктор просто инициализирует позицию нулями 
       GameEntity::GameEntity()
       {
              m_Position.x = m_Position.y = 0.0f;
       }
А метод update вообще пока не содержит логики
       void GameEntity::update()
       {
       }

Собственно все. 
Теперь у нас есть модель, которую нужно визуализировать. Осталось только дописать вид и все будет в шоколаде. 

Всем доброй ночи.

воскресенье, 21 августа 2011 г.

Simple render. Описание идеи

Всем привет. 

Представляю вашему вниманию первую часть цикла для начинающих об отрисовке в играх. 
Сегодня мы рассмотрим идею в общем и набросаем стартовое приложение. 
Для работы с рендером я буду использовать библиотеку glut. В принципе она сейчас практически не используется, но нам подойдет идеально. Использование glut позволит мне сосредоточится на структуре классов и не отвлекаться на разбор OpenGL или DirectX.

Погнали. 
Идею построена на паттерне MVC, т.е. на разделении данных и их визуального отображения. 

Давайте рассмотрим основные классы и интерфейсы, их в моей схеме 3: 
Renderer - этот класс отвечает за весь рендер в целом, он хранит в себе список всех объектов, которые нужно отобразить в игре, знает порядок их отображения. Также он отвечает за различные действия с рендер конвеером, которые необходимо выполнить в начале каждого кадра и соответственно в конце. 
IEntity - этот интерфейс является базовым для всех объектов с данными, которые находятся в нашей игре. Каждая сущность имеет свой уникальный идентификатор. Одним словом  это model.
IRenderer - данный интерфейс является базовым для всех объектов рендера в игре. Он отвечает за визуализацию модели данных, т.е за визуализацию игровых сущностей. Это view.

Это все что касается основным типов данных нашей игре. Данный подход позволяет реализовать практически любой по сложности рендер, постепенно вырабатывая набор сущностей и способов их представления. 
Конечно при этом нужно будет дорабатывать и класс Renderer, добавляя в него новые возможности по настройке рендера.

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

int _tmain(int argc, _TCHAR* argv[])
{
     /// блок инициализации окна
     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
     glutInitWindowSize(800, 600);
     glutCreateWindow("Simple");
     /// устанавливаем различные калбек функции, которые будет вызывать glut
     glutDisplayFunc(renderScene);
     glutReshapeFunc(Reshape);
     glutKeyboardFunc(Keyboard);
     setupRC();
     // запускаем основной цикл приложения
     glutMainLoop();
     return 0;
}

Самый интересный для нас метод renderScene. Именно в него мы будем встраивать код по отрисовке. 
Сейчас он имеет следующий вид:

 void renderScene(void)
{
     //очищаем экран
     glClear(GL_COLOR_BUFFER_BIT);
     // устанавливаем матрицу для нашего объекта
     glPushMatrix();

     glTranslatef(-200.0f+pos, 0.0f, 0.0f);
     // отрисовываем квадрат красного цвета на экране.
     glColor3ub(255,0,0);
     glBegin(GL_QUADS);
         glVertex2f(200,400);
         glVertex2f(200,200);
         glVertex2f(400,200);
         glVertex2f(400,400);
     glEnd();

     glPopMatrix();
     // вызываем свап буфера для того, чтобы наша картинка появилась на кране монитора
     glutSwapBuffers();
     glutPostRedisplay();
}

На сегодня это все. В следующий раз я подробно пройдусь по интерфейсам, покажу реализацию класса Renderer. 
Весь код стартового приложения можно посмотреть здесь. Для использования glut нужно подключить статическую либу к проекту, а потом рядом с исполняемым файлом положить dll библиотеку. Все есть вот тут

Всем доброй ночи.

среда, 17 августа 2011 г.

Simple render. Вступление


Всем привет.

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

Описание разбил на несколько частей:
1. Общее описание и создание окна с помощью glut
2. Интерфейс IEntity
3. Интерфейс IRenderer
4. Renderer и запуск финального приложения.

Вот ссылка на исходники примера для тех, кому код понятнее, чем мои сбивчивые описания.
Для написания примера я использовал Microsoft Visual Studio C++ Express 2010.

Всем доброй ночи.

пятница, 1 апреля 2011 г.

cURLpp: реализация комбинации С++ и WEB

Недавно появилась необходимость реализовать инструмент, который будет взаимодействовать с веб REST сервисом: получать информацию об объектах, создавать новые объекты, изменять или удалять старые.

Естественно во весь рост встал вопрос о том какой язык использовать для реализации подобной штуки. Тем более, что времени в обрез. 

Вначале решил использовать C# и .Net Framework, но потом выяснились дополнительные требования:

  1. приложение должно быть кросплатформенным: Mac, PC UNIX
  2. приложение неплохо бы реализовать в виде плагина к SVN, который как известно реализован на плюсах. 

После недолгих рысканий набрел на библиотеку cURLpp, которая является оберткой над libcURL. Вот определение библиотеки на сайте:

a free and easy-to-use client-side URL transfer library, supporting FTPFTPSHTTPHTTPSGOPHERTELNETDICTFILE and LDAP. libcurl supports HTTPS certificates, HTTP POSTHTTP PUTFTP uploading, kerberos, HTTP form based upload, proxies, cookies, user+password authentication, file transfer resume, http proxy tunneling and more!

libcurl is highly portable, it builds and works identically on numerous platforms, including Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUXIRIXAIX, Tru64, Linux, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS and more…

libcurl is free, thread-safe, IPv6 compatible, feature rich, well supported and fast.

 

К сожалению еще не пробовал данную библиотеку на ощупь. Как только попробую, отпишусь. Пока все выглядит отлично, я смогу сделать все что мне нужно. 

 

А вам приходилось решать аналогичные задачи? Какие решения предпочли вы?

 

Всем доброй ночи.

 

четверг, 20 января 2011 г.

Command Window and Macros in Visual Studio. Part 2

Всем привет.

В прошлой статье я рассматривал работу с командной строкой в Microsoft Visual Studio. Сегодня мы продолжаем ковырять студию и рассмотрим работу с макросами в студии. Одним из самых мощных инструментов для автоматизации работы.

Поскольку рассматривать что либо на абстрактных примерах себе дороже, то для начала небольшая пред-история. 

У нас на проекте для сборки игры используется Nant. Тем кто не знаком с этой системой советую ознакомиться. А пока небольшая вводная. Nant  это система, которая позволяет выполнять из командной строки различные команды. Реализован он на С# и доступен вместе с исходниками. Но основным достоинством Nant является широкий набор команд, а также возможность писать свои собственные команды на любом скриптовом или компилируемом языке, главное чтобы этот скрипт или скомпилированное приложение можно было выполнить из командной строки. 

У нас на проекте с помощью Nant собирается все, начиная от артовых ресурсов и заканчивая исходным кодом. Соответственно есть команды BuildGameCode, BuildGameAnimation, BuildLevel  и т.д.

Для конфигурации команд используется конфигурационный xml файл. 

Единственное неудобство, что пишу код я в студии, а вот для запуска команд приходиться использовать что под руки попадется. Одно время запускал просто из виндовой командной строки, потом использовал Luancher, потом использовал Notepad++. Правда в последнем случае кодировать тоже приходилось в блокноте, что не всегда удобно. 

Вот после всех скитаний попробовал использовать макросы в Visual Studio и получилось достаточно неплохо.

Использование макросов имеет несколько неоспоримых плюсов:

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

Но как всегда есть и несколько минусов:

  • писать макросы можно только на Visual Basic .Net
  • макросы это единичные команды - они хороши когда нужно выполнить быстрое, конечное действие, но когда вам нужно, что постоянно выполнялся некий процесс в фоне, то лучше использовать плагины

И еще один момент, который я пока не могу занести в минусы - это создание диалоговых окон. Предположим вы хотите, чтобы при вызове вашей команды открылось диалоговое окно (которое вы предварительно задизайнили) потом вы ввели некие значения в поля окна и нажав клавишу "Enter" получили результат. Так вот я не знаю, можно ли создавать свои окна, не успел проверить. 

Вернемся к моему примеру. Итак я создал макрос, который позволяет запустить любую команду или набор команд из командной строки в студии. Лог при этом выводится в это же командное окно, что позволяет мне его потом легко проанализировать и сохранить в файл. Также я написал команду, которая позволяет поменять любое поле xml файла конфигурации, например имя уровня с которого нужно стартовать. 

Следующая возможность, которую я хотел получить от макросов, это возможность снипетты, которых нет в студии ни для С++, ни для Lua. 

С макросами этот момент также оказался достаточно простым. Я создал текстовый файл, некий шаблон, в котором есть специальные символы $1, $2 и т.д. При вызове команды, текст из файла вставляется в код и символы заменяются параметрами, которые были переданы в команду. Вот и у нас есть снипеты. 

Всем доброй ночи. 

пятница, 5 ноября 2010 г.

Command Window and Macros in Visual Studio. Part 1

Последнее время приходиться писать на Lua, а также постоянно работать с командной строкой.
Лучшим вариантом для редактирования Lua является блокнот Notepad++. Во всяком случае я пока ничего лучше не нашел. Бесплатная лицензия, наличие широкого выбора плагинов, хорошая скорость работы делают его очень привлекательным вариантом. 
Но не хочется переключатся в блокнот, студия ближе, да по ходу работу иногда приходиться править С++ код, искать различную информацию в С++ коде. А в данном моменте студия пока обходит блокнот.

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

Вариант первый - плагин.
Это самый первый вариант, который приходит в голову, особенно когда спишь и видишь славу Visual Assist-а и ReSharper-а. Сразу тянет создать свой аналогичный плагин только под Lua. 
Создал и сел думать, а какой же функционал туда забить. Не придумал, вернее его было так много, что на десяток плагинов хватит. А вот первой ключевой фичи, которая которая потянет за собой остальные я не нашел. Потом я нашел еще несколько причин не использовать плагин:

  • неясен функционал
  •  

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

  • необходимость думать о визуальном интерфейсе
  •  

И тогда я наконец понял, что мне нужно. 

Вариант второй - СКРИПТ.
Но не просто скрипт, а скрипт, который может взаимодействовать со студией, править файлы проекта, получать информацию о классах и т.д.
В качестве отправной точки поисков я выбрал CommandWindow. Здесь меня ждал сюрприз. 
CommandWindow позволяет делать все, вызвать любую команду, которая есть в студии. 

При наборе команды появляется окно подсказка с вариантами команд

Например команда File.OpenFile
Синтаксис следующий "File.OpenFile filename". Причем при наборе имени файла студия тут предлагает варианты, не хуже чем это делают Visual Assist и ReSharper.

Также есть еще одна дополнительная возможность: присвоение команде короткого имени. Это избавляет от необходимости писать длинную команду. Вот вариант короткой команды File.OpenFile

Команда для создания синонима команды называется Tools.Alias или просто alias. Если просто вызвать alias, то можно посмотреть список уже заданных сокращений
Кроме того она позволяет создать синоним для команды с определенным набором параметров. Например после вызова "alias openSealedFile File.OpenFile SealedClass.h" я смогу использовать openSealedFile для открытия файла SealedClass.h

И на закуску еще один бонус - CommandWindow работает в Visual Studio Express Edition

Все это хорошо, но не хватает еще одной вещи - собственно самих скриптов.
И об этом я расскажу завтра
А на сегодня еще один вопрос в качестве домашнего задания: какие еще интересные возможности Visual Studio вы знаете и используете?

Всем доброй ночи.

четверг, 28 октября 2010 г.

Выделение памяти в векторе

Всем привет
Решил сделать простенькую программу, которая бы наглядно показывала разницу между метода resize и reserve в классе vector из стандатной библиотеки.
Программа реализовалась за 10 минут:

#include "stdio.h"
#include "iostream"
#include "vector"
using namespace std;

int main()
{
std::vector intVector;
printf("Start: Size = %i, capacity = %i\n", intVector.size(), intVector.capacity());
intVector.resize(10);
printf("Resize 10: Size = %i, capacity = %i\n", intVector.size(), intVector.capacity());
intVector.reserve(25);
printf("Reseerve 25: Size = %i, capacity = %i\n", intVector.size(), intVector.capacity());

for(int i=0;i<16;i++)
{
intVector.push_back(i);
}
printf("Push_back: Size = %i, capacity = %i\n", intVector.size(), intVector.capacity());

return 0;
}

Все отлично работает. Красота.
А потом вспомнил, во всех книгах говорят о том, что размер вектора при добавлении новых элементов увеличивается в зависимости от некого коэффициента.
И решил, а давай ка проверю.
Проверяется очень просто переопределяем операторы new/delete и смотрим, сколько памяти реально выделяется при изменении размера вектора. Ниже код:

char mem[4096];
int startFreeBlock=0;

void* operator new(size_t blockSize);
void* operator new[](size_t blockSize);
void operator delete(void* block);
void operator delete[](void* block);

void* operator new(size_t blockSize)
{
int oldstartFreeBlock = startFreeBlock;
startFreeBlock += blockSize;
printf("New = %i, Adress = %i\n", blockSize, (int)(&mem[oldstartFreeBlock]));
return &mem[oldstartFreeBlock];
}
void* operator new[](size_t blockSize)
{
int oldstartFreeBlock = startFreeBlock;
startFreeBlock += blockSize;
printf("New = %i, Adress = %i\n", blockSize, (int)(&mem[oldstartFreeBlock]));
return &mem[oldstartFreeBlock];
}
void operator delete(void* block)
{
printf("Delete = %i\n", (int)(block));
}
void operator delete[](void* block)
{
printf("Delete[] = %i\n", (int)(block));
}

Код собрал и запустил под двумя компиляторами и вот результаты:

gcc-4.4.1:
Start: Size = 0, capacity = 0
New = 40, Adress = 4673568
Resize 10: Size = 10, capacity = 10
New = 100, Adress = 4673608
Delete = 4673568
Reserve 25: Size = 10, capacity = 25
New = 200, Adress = 4673708
Delete = 4673608
Push_back: Size = 26, capacity = 50
Delete = 4673708

Visual C++ 2008 Express:
Start: Size = 0, capacity = 0
New = 40, Adress = 4325880
Resize 10: Size = 10, capacity = 10
New = 100, Adress = 4325920
Delete = 4325880
Reseerve 25: Size = 10, capacity = 25
New = 148, Adress = 4326020
Delete = 4325920
Push_back: Size = 26, capacity = 37
Delete = 4326020

Как и ожидалось, в случае явной просьбы изменить размер вектора с помощью методов resize и reserve, приложение послушно все делает. Вектор увеличивается ровно до того размера, который я запросил. А вот в случае неявного изменения размера вектора при вызове метода push_back компиляторы делают это по разному. Компилятор gcc выделяет в два раза больше памяти чем Visual C++.

А константу, которая задает насколько больше будет вектор после отрабатывания push_back так и не нашел. Может кто знает, где ее найти, и можно ли ее изменять?

Всем доброй ночи.

пятница, 3 сентября 2010 г.

Функция set_new_handler

Нашел интересный метод в С++.
Называется set_new_handler и призван помочь нам в случае если не получилось выделить память.
Описание можно глянуть тут и тут
Ниже пример использования:

#include "stdafx.h"
#include
using namespace std;

void no_memory()
{
printf( "Failed to allocate memory!\n");
throw bad_alloc();
}

int _tmain(int argc, _TCHAR* argv[])
{
set_new_handler(no_memory);
while(1)
{
try
{
int* p = new int[1024*1024*256];
printf("successed\n");
}
catch(bad_alloc b)
{
return 0;
}
}
//delete []p;
return 0;
}

Сразу же возникает закономерный вопрос: а как же его использовать в практическом коде?
А ответ прост - никак.

На проекте может быть два способа работы с памятью:
  • ленивый - все отдается на откуп стандартному new и надеемся, что памяти у пользователя всегда хватит.
  • практичный - команда берет на себя все управление памятью, каждый байт подсчитан и задокументирован.
Начнем со второго случая. Если команда взяла на себя управление памятью, то new переопределен и стало быть метод установленный вызовом set_new_handler никогда не сработает. Да и не нужен он, при нормально написанном менеджере памяти.

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

Доброй всем ночи.

четверг, 5 августа 2010 г.

Final class в C++ 2

Месяц назад написал статью Final class в C++, в которой пытался решить проблему с запретом наследования в С++. Проблема решилась, но при этом возникло два вопроса.

Для опытных программистов эти вопросы не очень сложные, но написать ответы считаю нужным. Обещал все-таки.

С первым все достаточно просто. И звучит он следующим образом: "Почему переопределенные операторы new/delete не имеют доступа к приватным конструктору и деструктору?".
Ответ: потому, что конструктор и деструктор вызываются не из операторов new/delete, их вызывает внешний код, который встраивается компилятором.

Второй вопрос повеселее: "Почему именно виртуальное наследование запрещает наследование от класса объявленного финальным?"
Ответ находится по ссылке С++ Faq. Если по-русски то суть в том, что если в цепочке родителей есть виртуальное наследование, то наследник должен иметь прямой доступ к конструктору данного класса: constructors for virtual base classes anywhere in your class's inheritance hierarchy are called by the "most derived" class's constructor.

Всем доброй ночи.

среда, 7 июля 2010 г.

Final class в С++

Последнее время приходится проводить много собеседований. С одной стороны занимает много времени и сил, а с другой дополнительный стимул расти самому.
Вот недавно всплыл такой вопрос: можно ли запретить наследование от класса в С++. В Java есть такое понятие как final класс, т.е. класс от которого нельзя наследоваться.

Предположим есть некий класс А:

class A
{
public:
A(){}
~A(){}
};

и класс B

class B : public A //все отлично собирается
{
};


Необходимо сделать так, чтобы при попытке наследование компилятор выдавал сообщение об ошибке.

Первое, что приходит в голову это сделать конструктор и деструктор класса А приватными. Действительно после этого я не смогу наследоваться, но возникает другая проблема - я не могу создавать экземпляры класса А.
Пытаемся развить мысль далее. Что если переопределить для класса А операторы new и delete. Так как это будут операторы класса, то они смогут получить доступ к приватным конструктору и деструктору. Получается вот такой класс:

class A
{
private:
A(){}
~A(){}


public:
void* operator new(size_t size)
{
return ::operator new(size);
}

void operator delete(void* block)
{
::operator delete(block);
}
};

сlass B : public A //компилятор ругается
{
};


Но на самом деле это не работает. Переопределенные операторы new\delete все равно не получают доступа к приватным конструктору и деструктору класса А. Объявление операторов new\delete друзьями класса А тоже не помогает.

Второй мыслью (после часа попыток) было полезть во всемирную паутину и найти ответ там. Ответ я нашел, но не на все вопросы. Итак реализация final класса для С++ выглядит следующим образом:

class ClassLocker
{
friend class A;
private:
ClassLocker(){}
};

class A : public virtual ClassLocker
{
public:
A(){}
~A(){}
};

class B : public A //компилятор выдает сообщение об ошибке
{
};

Самое интересное, что если мы убрать виртуальное наследование класса А от класса ClassLocker, т.е. написать class A : public ClassLocker, то все отлично собирается. Слово virtual в данном случае является ключевым.

Итак в итоге есть два вопроса:
  • Почему переопределенные операторы new\delete не имеют доступа к приватном конструктору и деструктору?
  • Почему именно виртуальное наследование запрещает дальнейшее наследование?
Ответы на вопросы я дам в следующей статье.

Всем доброй ночи.

среда, 2 июня 2010 г.

C++: что лучше учится или учить

Привет всем.

Последние два месяца работаю по принципу: "Глаза боятся, а руки делают".Вот и сейчас такое же ощущение.В нашей компании открываем специальные курсы по обучению С++. Причем курс будет с уклоном в игровую специфику.
Набрали группу из 6 человек, завтра первое занятие.

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

Результатами поделюсь позже.

Всем доброй ночи.