[Перевод] Как на самом деле работает планировщик Kubernetes? |
while True:
pods = get_all_pods()
for pod in pods:
if pod.node == nil:
assignNode(pod)
cronjob
— единственный компонент Kubernetes, код которого был мною прочитан. Контроллер cronjob
перебирает все cron-задания, проверяет, что ни для одного из них не надо ничего делать, ожидает 10 секунд и бесконечно повторяет этот цикл. Очень просто!Pending
(когда узел не назначен на под). При перезагрузке планировщика под выходил из этого состояния (вот тикет).go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)
sched.scheduleOne
». А что происходит там?func (sched *Scheduler) scheduleOne() {
pod := sched.config.NextPod()
// do all the scheduler stuff for `pod`
}
NextPod()
? Откуда растут ноги?func (f *ConfigFactory) getNextPod() *v1.Pod {
for {
pod := cache.Pop(f.podQueue).(*v1.Pod)
if f.ResponsibleForPod(pod) {
glog.V(4).Infof("About to try and schedule pod %v", pod.Name)
return pod
}
}
}
podQueue
), и следующие поды приходят из неё.podInformer.Informer().AddEventHandler(
cache.FilteringResourceEventHandler{
Handler: cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if err := c.podQueue.Add(obj); err != nil {
runtime.HandleError(fmt.Errorf("unable to queue %T: %v", obj, err))
}
},
host, err := sched.config.Algorithm.Schedule(pod, sched.config.NodeLister)
if err != nil {
glog.V(1).Infof("Failed to schedule pod: %v/%v", pod.Namespace, pod.Name)
sched.config.Error(pod, err)
sched.config.Error
снова добавляет под в очередь, поэтому для него всё-таки будет предпринята повторная попытка обработки.Error
не всегда вызывалась, когда реально происходила ошибка. Мы сделали патч (патч был опубликован в том же issue — прим. перев.), чтобы вызывать её корректно, после чего восстановление стало происходить правильно. Класс!while True:
pods = get_all_pods()
for pod in pods:
if pod.node == nil:
assignNode(pod)
cronjob
). Очень здорово!ИспользуйтеSharedInformers
.SharedInformers
предлагают хуки для получения уведомлений о добавлении, изменении или удалении конкретного ресурса. Также они предлагают удобные функции для доступа к разделяемым кэшам и для определения, где кэш применим.
informer
(например, pod informer
), который отвечает за:cronjob
не использует информаторов (работа с ними всё усложняет, а в данном случае, думаю, ещё не стоит вопрос производительности), однако многие другие (большинство?) — используют. В частности, планировщик так делает. Настройку его информаторов можно найти в этом коде.Для надёжного повторного помещения в очередь выносите ошибки на верхний уровень. Для простой реализации с разумным откатом естьworkqueue.RateLimitingInterface
.
Главная функция контроллера должна возвращать ошибку, когда необходимо повторное помещение в очередь. Когда его нет, используйтеutilruntime.HandleError
и возвращайтеnil
. Это значительно упрощает изучение случаев обработки ошибок и гарантирует, что контроллер ничего не потеряет, когда это необходимо.
Watches и Informers будут «синхронизироваться». Периодически они доставляют вашему методуUpdate
каждый подходящий объект в кластере. Хорошо для случаев, когда может потребоваться выполнить дополнительное действие с объектом, хотя это может быть нужно и не всегда.
В случаях, когда вы уверены, что повторное помещение в очередь элементов не требуется и новых изменений нет, можно сравнить версию ресурса у нового и старого объектов. Если они идентичны, можете пропустить повторное помещение в очередь. Будьте осторожны. Если повторное помещение элемента будет пропущено при каких-либо ошибках, он может потеряться (никогда не попасть в очередь повторно).
informerFactory := informers.NewSharedInformerFactory(kubecli, 0)
// cache only non-terminal pods
podInformer := factory.NewPodInformer(kubecli, 0)
@brendandburns — что здесь планируется исправить? Я действительно против таких маленьких периодов повторной синхронизации, потому что они значительно скажутся на производительности.
Согласен с @wojtek-t. Если resync вообще когда-либо и может решить проблему, это означает, что где-то в коде есть баг, который мы пытаемся спрятать. Не думаю, что resync — правильное решение.
Метки: author shurup системное администрирование devops *nix блог компании флант kubernetes go |
Pygest #15. Релизы, статьи, интересные проекты из мира Python [01 августа 2017 — 14 августа 2017] |
Метки: author andrewnester разработка веб-сайтов программирование машинное обучение python django digest pygest machine learning flask дайджест web deep learning |
Как распознать scam ICO? Часть II. Зрим |
Метки: author Menaskop финансы в it учебный процесс в it исследования и прогнозы в it ico scam скамы оценка ico (ito tge) it- юрист |
PHP-Дайджест № 114 – свежие новости, материалы и инструменты (1 – 14 августа 2017) |
Свежая подборка со ссылками на новости и материалы. В выпуске: PHP 7.2.0 Beta 2, об истории и безопасности unserialize(), старт PSR HTTP Client, StackOverflow Driven Development, видео с конференций и митапов, и многое другое.
Приятного чтения!
PSA: Don't use unserialize() on untrusted input (see https://t.co/8GZb1xqE1u)
— Nikita Popov (@nikita_ppv) August 10, 2017
PHP will no longer treat unserialize() bugs are security bugs.
StackOverflowBuddy::substringBetweenTwoStrings('platypus', 'pl', 'us');
// atyp
Спасибо за внимание!
Если вы заметили ошибку или неточность — сообщите, пожалуйста, в личку.
Вопросы и предложения пишите на почту или в твиттер.
Прислать ссылку
Быстрый поиск по всем дайджестам
<- Предыдущий выпуск: PHP-Дайджест № 113
Метки: author pronskiy разработка веб-сайтов php дайджест php- ссылки symfony yii laravel zend reactphp psr |
Немного про ашибки в программном обеспечении и помощь теории конечного автомата |
Работая в сфере ИТ, волей-неволей замечаешь огромное количество ошибок в различном программном обеспечении. Будь то ошибки мировых вендоров Microsoft, или же в прикладных бизнес-решениях фирмы 1С, или же в хваленных Apple с детской фатальной ошибкой "1 января 1970". Что уж говорить про небольшие компании с небольшим штатом программистов?
Насколько ошибки в ПО являются естественным процессом? Давайте разберёмся. Всё ниже является исключительно авторским мнением, в том числе классификация, возможность применения теории конечного автомата, наблюдения.
Не так давно Samsung заявил о запуске сервиса Samsung Pay, а наличие у меня часов Samsung Gear 3 открывало мне новое баловство — оплачивать покупки прикосновением часов к терминалу. Не знаю какие трудности произошли при производстве продукта, но первые версии программы мне не позволяли произвести настройку. Лишь с выпуском нескольких релизов я смог использовать сервис.
Все производители делают ошибки в области программного обеспечения, какими бы они крупными и сертифицированными не были.
Умственный труд программистов постоянно требует нововведений. Они должны применять современные технологии для того, чтобы обеспечить бизнес-идею инвесторов и мечтателей. Информация и технологии очень быстро устаревают и поэтому программисту требуется быть на передовой, словно первопроходцу-исследователю преодолевать заросли в тайге, засушливые пустыни, ледяные торосы.
Сколько полегло исследователей в попытке пройти сложные маршруты: Г. Седов, Р. Скотт, В. Беринг... О чём это я?
Программист работает с новыми, не проторенными технологиями, в которых легко делать ошибки. Баги есть как в поставляемых модулях внешних разработчиков, так и в их применении в собственном ПО.
|
[Из песочницы] Расчет приоритета комбинаций в техасском холдеме (покере) на PHP |
function cardsCreation()
{
$arrayCards = [];
for ($i = 0; $i < 7; $i++) {
$card = mt_rand(2, 14); // создаю случайное число в диапозоне от 2 до 14
$multiplier = mt_rand(0, 3); //формирую случайным образом множитель
if (1 == $multiplier) { //если множитель равен 1, добавляю к числу 100
$card = $card + 100;
} else if (2 == $multiplier) { //если множитель равен 2, добавляю к числу 200
$card = $card + 200;
} else if (3 == $multiplier) { //если множитель равен 3, добавляю к числу 300
$card = $card + 300;
}
if (!in_array($card, $arrayCards)) { //проверяю, есть ли в массиве такое число, если нет добавляю его в массив
$arrayCards = array_merge($arrayCards, [$card]);
} else { // если число есть в массиве, откатываю цикл, чтобы сформировать новое число
$i--;
}
}
return $arrayCards;
}
function straight(array $arrayCards) {
$newArrayCards = [];
$ace = false;
foreach ($arrayCards as $arrayCard) {
$newArrayCards = array_merge($newArrayCards, [$arrayCard % 100]); //создаю массив с остатками от деления на 100
if(14 == $arrayCard % 100) { //проверка на туз для комбинации А, 2, 3, 4, 5
$ace = true;
}
}
if($ace == true) {
$newArrayCards = array_merge($newArrayCards, [1]); //если в массиве присутствует туз, добавляю к массиву 1
}
rsort($newArrayCards); //сортирую массив с числами в порядке убывания
$count = 0; //счетчик, к которому добавляется 1 в случае, если разница между текущим элементом цикла и предыдущим = 1
$length = 4; //число, показывает до какого элемента массива проверять
$result = 0; //окончательный результат
$begin = 0; //число показывает, с какого элемента массива проверять
for($i = 1; $i <= $length; $i++) {
if (-1 == ($newArrayCards[$i] - $newArrayCards[$i - 1])) {
$count++;
}
if($length == $i) {
if(4 == $count) { //$count == 4, когда стрит
$result = $newArrayCards[$begin] * 1e+8;
}
else if($length < 6 or (6 == $length and $ace == true)) {//если стрита нет, идем еще на один круг
$length++; //увеличиваем число, до которого будем проверять
$begin++; //увеличиваем число, с которого будем проверять
$i = $begin; //при попадании в for к $i автоматически добавится 1 ($i++ в for)
$count = 0; //обнуляю счетчик
}
}
}
return $result;
}
function pokerFlush(array $arrayCards) {
$suit1 = []; //первая масть
$suit2 = []; //вторая масть
$suit3 = []; //третья масть
$suit4 = []; //четвертая масть
foreach ($arrayCards as $arrayCard) { //создаю 4 массива, содержащих разные масти исходного массива
if($arrayCard >= 2 and $arrayCard <= 14) {
$suit1 = array_merge($suit1, [$arrayCard]);
} else if($arrayCard >= 102 and $arrayCard <= 114) {
$suit2 = array_merge($suit2, [$arrayCard]);
} else if($arrayCard >= 202 and $arrayCard <= 214) {
$suit3 = array_merge($suit3, [$arrayCard]);
} else {
$suit4 = array_merge($suit4, [$arrayCard]);
}}
if(count($suit1) >= 5) { //если количество карт первой масти больше или равно 5
$result = straightFlush($suit1); //проверяю не образует ли данная комбинация стрит флеш
if(0 == $result) {//если стрит флеша нет
foreach ($suit1 as $key1 => $s1) {//выбираю остатки от деления на 100
$suit1[$key1] = $s1 % 100;
}
rsort($suit1); //сортирую массив по убыванию
$result = $suit1[0] * 1e+10 + $suit1[1] * 1e+8 + $suit1[2] * 1e+6 + $suit1[3] * 1e+4 + $suit1[4] * 1e+2;
}
} else if (count($suit2) >= 5) { //если количество карт второй масти больше или равно 5
$result = straightFlush($suit2);
if(0 == $result) {
foreach ($suit2 as $key2 => $s2) {
$suit2[$key2] = $s2 % 100;
}
rsort($suit2);
$result = $suit2[0] * 1e+10 + $suit2[1] * 1e+8 + $suit2[2] * 1e+6 + $suit2[3] * 1e+4 + $suit2[4] * 1e+2;
}
} else if (count($suit3) >= 5) { //если количество карт третьей масти больше или равно 5
$result = straightFlush($suit3);
if(0 == $result) {
foreach ($suit3 as $key3 => $s3) {
$suit3[$key3] = $s3 % 100;
}
rsort($suit3);
$result = $suit3[0] * 1e+10 + $suit3[1] * 1e+8 + $suit3[2] * 1e+6 + $suit3[3] * 1e+4 + $suit3[4] * 1e+2;
}
} else if (count($suit4) >= 5) { //если количество карт четвертой масти больше или равно 5
$result = straightFlush($suit4);
if(0 == $result) {
foreach ($suit4 as $key4 => $s4) {
$suit4[$key4] = $s4 % 100;
}
rsort($suit4);
$result = $suit4[0] * 1e+10 + $suit4[1] * 1e+8 + $suit4[2] * 1e+6 + $suit4[3] * 1e+4 + $suit4[4] * 1e+2;
}
} else {
$result = 0;
}
return $result;
}
function straightFlush(array $arrayCards) {
$newArrayCards = [];
$ace = false;
foreach ($arrayCards as $arrayCard) {
$newArrayCards = array_merge($newArrayCards, [$arrayCard % 100]);
if (14 == $arrayCard % 100) {
$ace = true;
}
}
if ($ace == true) {
$newArrayCards = array_merge($newArrayCards, [1]);
}
rsort($newArrayCards);
$count = 0;
$length = 4;
$result = 0;
$begin = 0;
for ($i = 1; $i <= $length; $i++) {
if (-1 == ($newArrayCards[$i] - $newArrayCards[$i - 1])) {
$count++;
}
if ($length == $i) {
if (4 == $count) {
$result = $newArrayCards[$begin] * 1e+16;
} else if ((7 == count($arrayCards) and ($length < 6 or (6 == $length and $ace == true))) or
(6 == count($arrayCards) and ($length < 5 or (5 == $length and $ace == true))) or //если число элементов исходного массива = 6
(5 == count($arrayCards) and (5 == $length and $ace == true))) { //если число элементов исходного массива = 5
$length++;
$begin++;
$i = $begin;
$count = 0;
}
}
}
return $result;
}
function couple(array $arrayCards) {
$newArrayCards = [];
foreach ($arrayCards as $arrayCard) {
$newArrayCards = array_merge($newArrayCards, [$arrayCard % 100]); //создаю массив с остатками от деления на 100
}
rsort($newArrayCards); //сортирую массив с числами в порядке убывания
$count1 = 0; //счетчик для первой пары
$count2 = 0; //счетчик для второй пары
$count3 = 0; //счетчик для третьей пары
$match1 = 0; //сюда положу значение первой пары
$match2 = 0; //сюда положу значение второй пары
$match3 = 0; //сюда положу значение третьей пары
for($i = 1; $i < count($newArrayCards); $i++) {
if ($newArrayCards[$i] == $match1 or $match1 == 0) { //первое парное сочетание
if ($newArrayCards[$i] == $newArrayCards[$i - 1]) {
$match1 = $newArrayCards[$i];
$count1++;
}
} else if ($newArrayCards[$i] == $match2 or $match2 == 0) { //второе парное сочетание
if ($newArrayCards[$i] == $newArrayCards[$i - 1]) {
$match2 = $newArrayCards[$i];
$count2++;
}
} else if ($newArrayCards[$i] == $match3 or $match3 == 0) { //третье парное сочетание
if ($newArrayCards[$i] == $newArrayCards[$i - 1]) {
$match3 = $newArrayCards[$i];
$count3++;
}
}
}
//здесь я преобразую 111 к 110 (2 пары) и 211 к 210 (фулл хаус)
if(($count1 == 1 or $count1 == 2) and $count2 == 1 and $count3 == 1) {
$count3 = 0;
}
//здесь я преобразую 121 сначала к 211 для простоты вычислений, а затем к 210 (фулл хаус)
else if($count2 == 2 and $count1 == 1 and $count3 == 1) {
$support = $match2;
$match2 = $match1;
$match1 = $support;
$count1 = 2;
$count2 = 1;
$count3 = 0;
}
//здесь я преобразую 112 сначала к 211 для простоты вычислений, а затем к 210 (фулл хаус)
else if($count3 == 2 and $count1 == 1 and $count2 == 1) {
$support = $match3;
$match2 = $match1;
$match1 = $support;
$count1 = 2;
$count3 = 0;
}
//здесь я преобразую 220 к 210 (фулл хаус)
else if($count1 == 2 and $count2 == 2 and $count3 == 0) {
$count2 = 1;
}
//здесь я преобразую 120 к 210 (фулл хаус)
else if ($count1 == 1 and $count2 == 2 and $count3 == 0) {
$support = $match1;
$match1 = $match2;
$match2 = $support;
$count1 = 2;
$count2 = 1;
}
//320 к 300 и 310 к 300
else if($count1 == 3 and ($count2 == 2 or $count2 == 1)) {
$count2 = 0;
}
//230 к 320 и затем к 300 и 130 к 310 и затем к 300
else if($count2 == 3 and($count1 == 2 or $count1 == 1)) {
$support = $match2;
$match2 = $match1;
$match1 = $support;
$count1 = 3;
$count2 = 0;
}
//каре
if ($count1 == 3) {
$count1 = 1e+14;
}
//фулл хаус
else if ($count1 == 2 and $count2 == 1) {
$count1 = 1e+12;
$count2 = 1e+10;
}
//тройка
else if ($count1 == 2 and $count2 == 0) {
$count1 = 1e+6;
}
//2 пары
else if ($count1 == 1 and $count2 == 1) {
$count1 = 1e+4;
$count2 = 1e+2;
}
//пара
else if ($count1 == 1 and $count2 == 0) {
$count1 = 1e+2;
}
$result = $match1 * $count1 + $match2 * $count2;// $match1 и $match2 будут равны 0, если совпадений не было
return $result;
}
function priority(array $arrayCards) {
//здесь я определяю старшую карту
if($arrayCards[5] % 100 > $arrayCards[6] % 100) { //условились, что две последние карты массива - карманные
$highCard1 = $arrayCards[5] % 100;
$highCard2 = $arrayCards[6] % 100;
} else {
$highCard1 = $arrayCards[6] % 100;
$highCard2 = $arrayCards[5] % 100;
}
$flush = pokerFlush($arrayCards); //вызываю функцию для расчета флеша
$straight = straight($arrayCards); //вызываю функцию для расчета стрита
$couple = couple($arrayCards); //вызываю функцию для расчета пар
//далее определяю результат согласно приоритету комбинаций
if($flush >= 1e+16) {
$result = $flush; //стрит флеш
} else if($couple >= 1e+14 and $couple < 1e+16) {
$result = $couple; //каре
} else if($couple >= 1e+12 and $couple < 1e+14) {
$result = $couple; //фулл хаус
} else if($flush >= 1e+10) {
$result = $flush; //флеш
} else if($straight >= 1e+8 and $straight < 1e+10) {
$result = $straight; //стрит
} else if($couple >= 1e+6 and $couple < 1e+8) {
$result = $couple; //тройка
} else if($couple >= 1e+4 and $couple < 1e+6) {
$result = $couple; //две пары
} else if($couple >= 1e+2 and $couple < 1e+4) {
$result = $couple; //пара
} else {
$result = $highCard1 + $highCard2 * 1e-2; //старшая карта
}
return $result;
}
100/12 * ($arrayCards[$i] % 100 - 2)
100/4 * floor($arrayCards[$i] / 100)
Метки: author heartsease php poker texas holdem |
TSP problem. Mixed algorithm |
Метки: author Grossmend алгоритмы |
Итак, вы решили развернуть OpenStack |
Метки: author divanikus системное администрирование облачные вычисления виртуализация openstack облака |
5 популярных мифов о дизайне логотипов |
Метки: author Logomachine брендинг блог компании логомашина дизайн логотип миф видео |
Дайджест интересных материалов для мобильного разработчика #216 (6 августа — 13 августа) |
![]() |
Сначала они воруют, а когда ты побеждаешь, то тебя убивают |
![]() |
Заблуждения Clean Architecture |
![]() |
Разработка интерфейса приложения для пожизненного использования на примере мобильного дневника диабета |
|
Optimization Unity3d UI by GPU (for example minimap) или создаем миникарту без дополнительных камер и спрайтов |
streeter12 4 июля 2016 в 14:51
Данный метод несмотря на свою простоту имеет явные недостатки.
1. Низкая производительность.
2. Для добавления новых меток надо создавать новые сферы (лишний хлам в префабах).
3. Добавление новых типов меток и их фильтров для различных игроков сильно затруднено.
4. Для смены внешнего вида метки необходимо создавать меш!
5. Лишние объекты на каждой сцене => лишняя сложность => сложнее разработка и поддержка.
6. Сложно тестировать => больше возможных багов (с учетом 3).
static const int MaxCount = 256;
float4 _Targets[MaxCount];
float _WorldSizeX;
float _WorldSizeY;
protected virtual void Update ()
{
for (int i = 0; i < list.Length; i++)
{
list[i] = new Vector4(0, 0, 0, -1);
}
for (int i = 0; i < list.Length && i < targets.Count ; i++)
{
list[i] = new Vector4(targets[i].x *k.x, targets[i].y, targets[i].z*k.y, targets[i].w);
}
image.GetModifiedMaterial(image.material).SetVectorArray("_Targets", list);
}
fixed a = _Targets[i].y;
fixed resultX = tX;
fixed resultY = tY;
if (a != 0)
{
fixed x0 = minX + (sizeX * 0.5);
fixed y0 = minY + (sizeY * 0.5);
resultX = x0 + (tX - x0) * cos(a) - (tY - y0) * sin(a);
resultY = y0 + (tY - y0) * cos(a) + (tX - x0) * sin(a);
}
protected virtual void Start ()
{
tests = new List();
float x = 10;
float y = 10;
for (int i = 0; i < 256; i++)
{
int idx = Random.Range(0, prefabs.Count);
GameObject obj = Instantiate(prefabs[idx].gameObject);
RectTransform t = obj.GetComponent();
t.SetParent(parent);
t.localPosition = new Vector3(startPosition.x + x, startPosition.y + y, 0);
if (useRotation)
{
t.rotation = Quaternion.Euler(0, 0, Random.Range(0, 360));
}
x += step;
if (i % 20 == 0)
{
y += step;
x = 0;
}
tests.Add(t);
}
}
Заполнения через прокси класс для шейдера
item.type = Random.Range(0, 64); означает тип иконки
void Start () {
tests = new List();
float x = 10;
float z = 10;
for (int i = 0; i < 256; i++)
{
Item2 item = new Item2();
item.position = new Vector3(x, 0, z);
if (useRotation)
{
item.rotation = Random.Range(0, 360);
}
item.type = Random.Range(0, 64);
x += step;
if (i % 20 == 0)
{
z += step;
x = 0;
}
tests.Add(item);
}
}
Профайлер для шейдера (на сцене еще 3 куба)

Профайлер для спрайтов (на сцене еще 3 image в качестве префабов)

и напоследок пример с маской.

P.S.
"Если можешь что-то посчитать на GPU, делай это"
Метки: author derek_streyt разработка игр клиентская оптимизация unity3d c# optimization gpu shader ui |
Мастер, победивший смерть… через Power Point. Рецензия на книгу А. Каптерева «Мастерство презентации» |
изысканный, сложный шрифт хорош для запоминания, а легкий, простой — для призыва к действию.
Если слайд, призывающий к действию, визуально сложный, то аудитория с большей вероятностью запомнит его, но с меньшей — возьмется за выполнение задачи.
Большинство хороших диаграмм или двигают что-то куда-то или что-то с чем-то сравнивают.
Несмотря на то, что вы в положении учителя, будьте готовы учиться. Если вы не хотите учиться, никто из присутствующих не захочет.
Метки: author meteor-city читальный зал презентация публичное выступление дизайн презентации алексей каптерев мастерство презентации |
[Из песочницы] Получение записей телефонной книги с мобильного телефона без дисплея |
Метки: author yaanyk восстановление данных телефонная книга |
[Из песочницы] Безопасный OpenVPN на VPS за несколько минут |
Метки: author xl-tech системное администрирование сетевые технологии *nix vpn openvpn vps блокировки информационная безопасность скрипт bash- |
Изоляция css стилей с помощью компонентного подхода |
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Метки: author x07 разработка веб-сайтов javascript html css gulp-plugin components html- верстка |
Использование ImGui с SFML для создания инструментов для разработки игр |
Привет, Хабр!
Данная статья — вольный перевод моей статьи на русский с некоторыми небольшими изменениями и улучшениями. Хотелось бы показать как просто и полезно использовать ImGui с SFML. Приступим.
При разработке игр очень важно иметь хорошие инструменты для создания контента (редактор уровней, ресурсов и пр.), а также для дебаггинга. Наличие данных вещей повышает производительность и креативность. Гораздо проще отлавливаются баги и исправляются уже найденные: легко выводить значение различных переменных, а также создавать виджеты для их изменения, чтобы посмотреть, что происходит с игрой при определённых их значениях. Виджеты для изменения переменных также очень полезны для полировки геймплея. Например, можно легко изменять скорость передвижения персонажей, скорость перезарядки оружия и пр.
Вот какие инструменты я создал с помощью ImGui для своей игры:
Редактор уровней
Консоль Lua
Редактор анимаций
Как можно видеть, в ImGui есть достаточно разных виджетов, чтобы создавать полезные и удобные интерфейсы.
Immediate mode GUI немного отличается от классической методики программирования интерфейсов, которая называется retained mode GUI. ImGui виджеты создаются и рисуются в каждом кадре игрового цикла. Сами виджеты не хранят внутри себя своё состояние, либо хранят абсолютно минимальный необходимый минимум, который обычно скрыт от программиста.
В отличие от того же Qt, где для создания кнопки нужно создавать объект QPushButton
, а затем связывать с ней какую-нибудь функцию-callback, вызываемую при нажатии, в ImGui всё делается гораздо проще. В коде достаточно написать:
if (ImGui::Button("Some Button")) {
... // код, вызываемый при нажатии кнопки
}
Данный код должен вызываться в каждой итерации игрового цикла, в которой эта кнопка должна быть доступна пользователю.
Изначально данный концепт может показаться странным и очень неэффективным, однако это всё работает так быстро в сравнении с остальным кодом, что в результате даже сложные интерфейсы не привносят сильных изменений в производительность игры.
Советую посмотреть вот это видео Кейси Муратори про ImGui, если вы хотите узнать чуть больше о данной методике.
Итак, каковы же достоинства ImGui?
Итак, начнём
Добавьте следующие файлы в билд вашего проекта:
Вот небольшой пример кода, который создаёт окошко ImGui и позволяет менять цвет заднего фона и заголовок окна. Объяснения того, что происходит, будут далее.
#include "imgui.h"
#include "imgui-sfml.h"
#include Graphics/RenderWindow.hpp>
#include System/Clock.hpp>
#include Window/Event.hpp>
int main()
{
sf::RenderWindow window(sf::VideoMode(640, 480), "");
window.setVerticalSyncEnabled(true);
ImGui::SFML::Init(window);
sf::Color bgColor;
float color[3] = { 0.f, 0.f, 0.f };
// здесь мы будем использовать массив char. Чтобы использовать
// std::string нужно сделать действия, описанные во второй части
char windowTitle[255] = "ImGui + SFML = <3";
window.setTitle(windowTitle);
sf::Clock deltaClock;
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
ImGui::SFML::ProcessEvent(event);
if (event.type == sf::Event::Closed) {
window.close();
}
}
ImGui::SFML::Update(window, deltaClock.restart());
ImGui::Begin("Sample window"); // создаём окно
// Инструмент выбора цвета
if (ImGui::ColorEdit3("Background color", color)) {
// код вызывается при изменении значения, поэтому всё
// обновляется автоматически
bgColor.r = static_cast(color[0] * 255.f);
bgColor.g = static_cast(color[1] * 255.f);
bgColor.b = static_cast(color[2] * 255.f);
}
ImGui::InputText("Window title", windowTitle, 255);
if (ImGui::Button("Update window title")) {
// этот код выполняется, когда юзер жмёт на кнопку
// здесь можно было бы написать
// if(ImGui::InputText(...))
window.setTitle(windowTitle);
}
ImGui::End(); // end window
window.clear(bgColor); // заполняем окно заданным цветом
ImGui::SFML::Render(target);
window.display();
}
ImGui::SFML::Shutdown();
}
Вы должны увидеть что-то вроде этого:
Попробуйте изменить что-нибудь. Если кликнуть два раза на одно из полей RGB, то можно ввести соответствующее значение. Если одно из полей потянуть, то можно плавно изменять текущее введённое значение. Поле ввода позволяет изменить заголовок окна после нажатия на кнопку.
Отлично, теперь разберёмся как всё работает.
ImGui инициализируется вызовом ImGui::SFML::Init
, при вызове в функцию передаётся ссылка на окно sf::RenderWindow
. В этот момент также создаётся стандартный шрифт, который будет использоваться в дальнейшем. (см. раздел Fonts how-to в описании imgui-sfml, чтобы увидеть как использовать другие шрифты).
При выходе из программы важно вызывать функцию ImGui::SFML::Shutdown
, которая освобождает ресурсы, которые использует ImGui.
В игровом цикле ImGui имеет две фазы: обновление и рендеринг.
Обновление состоит из обработки событий, обновления состояния ImGui и обновления/создания виджетов. Обработка событий происходит через вызов ImGui::SFML::ProcessEvent
. ImGui обрабатывает события клавиатуры, мыши, изменения фокуса и размера окна. Обновление состояния ImGui производится в ImGui::SFML::Update
, в неё передаётся delta time (время между двумя обновлениями), который ImGui использует для обновления состояния виджетов (например, для анимации). Также в данной функции вызывается ImGui::NewFrame
, после вызова которой уже можно создавать новые виджеты.
Рендеринг ImGui осуществляется вызовом ImGui::SFML::Render
. Очень важно создавать/обновлять виджеты между вызовами ImGui::SFML::Update
и ImGui::SFML::Render
, иначе ImGui будет ругаться на нарушение состояния.
Если вы рендерите реже, чем обновляете ввод и игру, то в конце каждой итерации вашего update
необходимо также вызывать ImGui::EndFrame
:
while (gameIsRunning) {
while (updateIsNeeded()) {
updateGame(dt);
ImGui::SFML::Update(window, dt);
ImGui::EndFrame();
}
renderGame();
}
Виджеты создаются путём вызова соответствующих функций (например, ImGui::InputInt
или ImGui::Button
). Если вызвать ImGui::ShowTestWindow
, то можно увидеть много примеров использования ImGui, весь код можно найти в imgui_demo.cpp.
Для SFML в биндинге были созданы некоторые перегрузки функций, например в ImGui::Image
и ImGui::ImageButton
можно кидать sf::Sprite
и sf::Texture
, также можно легко рисовать линии и прямоугольники вызовом DrawLine
, DrawRect
и DrawRectFilled
.
Вот такая библиотека: проста в использовании и настройке, и очень полезна для создания инструментов и дебагинга. Приятного использования!
P.S. Если возникнет интерес, то могу перевести и вторую часть туториала, которая рассказывает про использование ImGui с современным C++ и стандартной библиотекой. Советую обратить на статью внимание тем, кто решит использовать (или уже использует) ImGui: она показывает как просто решать основные проблемы ImGui и делать всё проще и безопаснее, чем это делается в C++03.
Метки: author eliasdaler разработка игр программирование c++ sfml gamedev gamedevelopment gui imgui tutorial |
Проклятые Земли — Улучшаем бег и опыт с напарниками |
.text:00548315 loc_548315:
.text:00548315 fld dword ptr [edi+14h]
.text:00548318 fld dword ptr [edi+18h]
.text:0054831B fmul ds:dbl_73F088
.text:00548321 fsubp st(1), st
.text:00548323 fst dword ptr [edi+14h]
.text:00548326 > fcomp ds:flt_73B858
.text:0054832C fnstsw ax
.text:0054832E test ah, 1
.text:00548331 jz short loc_548388
fst dword ptr [edi+14h]
. Можно поставить Operand type — Floating point на этот адрес и на соседний [edi+18h]
.[edi+14h]
— текущее значение[edi+18h]
— максимальное значениеdbl_73F088
, и результат вычитается из текущего значения. Поэтому все персонажи бегают одинаково..rdata:0073F088 dbl_73F088 dq 6.666666666666666e-3
; 6.666666666666666e-3 = 0.006666666666666666 = 1/150
4E 1B E8 B4 81 4E 7B 3F
0.0066666666666666667 - 0x3F7B4E81B4E81B4F
0.006666666666666666 - 0x3F7B4E81B4E81B4E
0.0022222222222222222 - 0x3F623456789ABCDF
0.002222222222222222 - 0x3F623456789ABCDE
0x3F623456789ABCDF
DF BC 9A 78 56 34 62 3F
rep movsd
..text:00522D00 fld dword ptr [ebx+700h]
.text:00522D06 fadd dword ptr [ebx+4]
.text:00522D09 fsub dword ptr [ebx+8]
.text:00522D0C fstp [ebp+var_10]
.text:00522D0F fld [ebp+var_10]
.text:00522D12 fistp [ebp+var_C]
.text:00522D15 mov edx, [ebp+var_C]
.text:00522D18 mov [edi+8], edx
.text:00522D1B > mov eax, [ebx+10h]
.text:00522D1E mov [ebp+var_10], eax
ebx
находится адрес объекта персонажа. В [ebx+700h]
находится 0.[ebx+4]
..text:005239D7 fld ds:dbl_73E128
.text:005239DD fld dword ptr [esi+20h]
.text:005239E0 fsub ds:flt_73E124
.text:005239E6 call __CIpow
.text:005239EB fmul [ebp+arg_4]
.text:005239EE fadd dword ptr [esi+700h]
.text:005239F4 fcom ds:flt_73B858
.text:005239FA fst dword ptr [esi+700h]
.text:00523A00 fnstsw ax
.text:00523A02 test ah, 41h
.text:00523A05 jnz short loc_523A19
.text:00523A07 fadd dword ptr [esi+4]
.text:00523A0A mov dword ptr [esi+700h], 0
.text:00523A14 fstp dword ptr [esi+4]
.text:00523A17 > jmp short loc_523A1B
[esi+4]
новое значение опыта. В [ebp+arg_4]
число 2.0. За молодого кабана дают 4.0, значит деление находится до вызова функции..text:00591521 loc_591521:
.text:00591521 fild [ebp+var_18]
.text:00591524 xor esi, esi
.text:00591526 cmp eax, edi
.text:00591528 mov [ebp+var_14], esi
.text:0059152B fdivr [ebp+arg_4]
.text:0059152E fstp [ebp+arg_4]
.text:00591531 jle short loc_5915A5
.text:00591533 jmp short loc_591537
.text:00591324 fdiv [ebp+var_18]
// было
.00591521: DB45E8 fild d,[ebp][-018]
.00591524: 33F6 xor esi,esi
.00591526: 3BC7 cmp eax,edi
.00591528: 8975EC mov [ebp][-014],esi
.0059152B: D87D0C fdivr d,[ebp][00C]
.0059152E: D95D0C fstp d,[ebp][00C]
.00591531: 7E72 jle .0005915A5
.00591533: EB02 jmps .000591537
// стало
.00591521: 909090 nop
.00591524: 33F6 xor esi,esi
.00591526: 3BC7 cmp eax,edi
.00591528: 8975EC mov [ebp][-014],esi
.0059152B: 909090 nop
.0059152E: 909090 nop
.00591531: 7E72 jle .0005915A5
.00591533: EB02 jmps .000591537
Метки: author michael_vostrikov реверс-инжиниринг разработка игр evil islands проклятые земли бег персонажа опыт напарников |
[Перевод] Переосмысление PID 1. Часть 2 |
Метки: author houk системное администрирование серверное администрирование настройка linux *nix systemd linux sysvinit |
Информационная экономика: почему стоимость технологических компаний так высока |
Метки: author itinvest финансы в it блог компании itinvest финансы ит биржа капитализация инвестиции |
[Из песочницы] Разработка telegram бота с использованием Spring |
Пишите телеграмм ботов? Ваша производительность разработки желает лучшего? Ищите чего-то нового? Тогда прошу под кат.
Идея заключается в следующем: слямзить архитектуру spring mvc и перенести на telegram api.
Выглядеть должно как-то так:
@BotController
public class SimpleOkayController {
@BotRequestMapping(value = "/ok")
public SendMessage ok(Update update) {
return new SendMessage()
.setChatId(update.getMessage().getChatId())
.setText("okay bro, okay!");
}
}
или
@BotController
public class StartController {
@Autowired
private Filter shopMenu;
@Autowired
private PayTokenService payTokenService;
@Autowired
private ItemService itemService;
@BotRequestMapping("/shop")
public SendMessage generateInitMenu(Update update) {
return new SendMessage()
.setChatId(update.getMessage().getChatId().toString())
.setText("Товары моего магазинчика!")
.setReplyMarkup(shopMenu.getSubMenu(0L, 4L, 1L)); // <--
}
@BotRequestMapping(value = "/buyItem", method = BotRequestMethod.EDIT)
public List bayItem(Update update) {
....................
Item item = itemService.findById(id); // <--
return Arrays.asList(new EditMessageText()
.setChatId(update.getMessage().getChatId())
.setMessageId(update.getMessage().getMessageId())
.setText("Подтвердите ваш выбор, в форме ниже"),
new SendInvoice()
.setChatId(Integer.parseInt(update.getMessage().getChatId().toString()))
.setDescription(item.getDescription())
.setTitle(item.getName())
.setProviderToken(payTokenService.getPayToken())
........................
.setPrices(item.getPrice())
);
}
}
Это даёт следующие преимущества:
Давайте теперь посмотрим как это можно завести в нашем проекте
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface BotController {
String[] value() default {};
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BotRequestMapping {
String[] value() default {};
BotRequestMethod[] method() default {BotRequestMethod.MSG};
}
Создаем свой контейнер обработчиков в виде обычной HashMap
public class BotApiMethodContainer {
private static final Logger LOGGER = Logger.getLogger(BotApiMethodContainer.class);
private Map controllerMap;
public static BotApiMethodContainer getInstanse() {
return Holder.INST;
}
public void addBotController(String path, BotApiMethodController controller) {
if(controllerMap.containsKey(path)) throw new BotApiMethodContainerException("path " + path + " already add");
LOGGER.trace("add telegram bot controller for path: " + path);
controllerMap.put(path, controller);
}
public BotApiMethodController getBotApiMethodController(String path) {
return controllerMap.get(path);
}
private BotApiMethodContainer() {
controllerMap = new HashMap<>();
}
private static class Holder{
final static BotApiMethodContainer INST = new BotApiMethodContainer();
}
}
В контейнере будем хранить контроллеры обертки (для пары @BotController и @BotRequestMapping)
public abstract class BotApiMethodController {
private static final Logger LOGGER = Logger.getLogger(BotApiMethodController.class);
private Object bean;
private Method method;
private Process processUpdate;
public BotApiMethodController(Object bean, Method method) {
this.bean = bean;
this.method = method;
processUpdate = typeListReturnDetect() ? this::processList : this::processSingle;
}
public abstract boolean successUpdatePredicate(Update update);
public List process(Update update) {
if(!successUpdatePredicate(update)) return null;
try {
return processUpdate.accept(update);
} catch (IllegalAccessException | InvocationTargetException e) {
LOGGER.error("bad invoke method", e);
}
return null;
}
boolean typeListReturnDetect() {
return List.class.equals(method.getReturnType());
}
private List processSingle(Update update) throws InvocationTargetException, IllegalAccessException {
BotApiMethod botApiMethod = (BotApiMethod) method.invoke(bean, update);
return botApiMethod != null ? Collections.singletonList(botApiMethod) : new ArrayList<>(0);
}
private List processList(Update update) throws InvocationTargetException, IllegalAccessException {
List botApiMethods = (List) method.invoke(bean, update);
return botApiMethods != null ? botApiMethods : new ArrayList<>(0);
}
private interface Process{
List accept(Update update) throws InvocationTargetException, IllegalAccessException;
}
}
Теперь когда у нас есть данная кодовая база возникает вопрос: как Spring заставить автоматически наполнять контейнер, чтобы мы могли им пользоваться?
Для этого реализуем специальный бин — BeanPostProcessor. Это дает возможность отлавливать бины во время их инициализации. Наши контроллеры имеют scope по умолчанию — синглтон, значит инициализироваться они будут со стартом контекста!
@Component
public class TelegramUpdateHandlerBeanPostProcessor implements BeanPostProcessor, Ordered {
private static final Logger LOGGER = Logger.getLogger(TelegramUpdateHandlerBeanPostProcessor.class);
private BotApiMethodContainer container = BotApiMethodContainer.getInstanse();
private Map botControllerMap = new HashMap<>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class
Метки: author PqDn java spring telegram bots |