Вы, наверное, слышали и встречали такую штуку, а некоторые даже писали.

instance.Method1().Method2()

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

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

Как? Ну это довольно просто:

void Method() {}

Меняем на

T Method() { return this; }

Где T может быть текущим типом, может быть интерфейсом (как в оригинале было и задумано, но структуры все портят).

Зачем? Лаконичность кода и легкость восприятия.

Пишите в комментах где вы используете fluent-interface подход.

Read More  

Есть такой мемчик, где люди делятся на 2 типа и вот это все. Ну там где у человека выключено отображение ворнингов и там где включено.

Я отношусь к тем, у кого они включены и вообще не люблю когда в логах мусор.

Так вот, в шарпе есть такая штука:

#pragma warning disable
#pragma warning restore

Она позволяет включать и выключать либо все ворнинги, либо конкретные, если указано какие конкретно нужно отключить.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#pragma-warning

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

Поддерживайте ваш код в чистоте и без лишних логов и предупреждений.

Read More  

Это приведение к единичному размеру, при этом направление сохраняется. Обычно мы используем для этого v.normalized или v.Normalize(). Второй вариант будет немного быстрее первого, т.к. мы не создаем копию вектора, а изменяем существующий.

Но как оно работает внутри? Как нетрудно догадаться из определения нормализации, чтобы привести вектор к единичному - нам нужна его длина, а длина вектора - это корень по теореме Пифагора. Поэтому если вы по какой-то причине уже получили длину вектора, то просто поделите (ну поделите, ага https://t.me/unsafecsharp/150):

var inv_length = 1f / length;
v.x *= inv_length;
v.y *= inv_length;

И получите нормализованный вектор.

А то я часто встречаю примерно такой код в хот частях:

var length = v.magnitude;
var n = v.normalized;

Хотя на деле проще было бы просто получить длину один раз и использовать ее дважды (ну или сделать свой метод для этого).

Read More  

Я уже делал пост про FixedUpdate (https://t.me/unsafecsharp/103), но не писал про разницу между fixedDeltaTime и deltaTime.

В методе Update deltaTime будет равен времени, которое прошло с прошлого кадра, при этом fixedDeltaTime будет равен фиксированной величине.

А вот в FixedUpdate fixedDeltaTime останется, а вот deltaTime будет равен fixedDeltaTime. Другими словами, можно использовать deltaTime внутри FixedUpdate.

Read More  

Мы часто используем этот метод для динамического создания атласа, когда, например, мы загружаем аватарки игроков, а какие они будут мы заранее не знаем. Или мы формируем атлас для боя, когда игроки могут выбрать какие-нибудь скины юнитов, а нам все еще нужен 1 draw call ;)

Read More  

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

Есть способ комбинировать меши и без этого: mesh.CombineMeshes.

Но придется передавать туда уже структуры, в которых описываются матрицы и мешки.

Мы такое часто использовали для 3D, когда мы создавали одну мешку и вместо 1к объектов получали всего один. Есстественно, нужно понимать, что батчить нужно по-материально, т.е. какой-то код с этой логикой нужно будет все же написать.

Read More  

Если вы не используете параметр <span>out</span>, по которому возвращается значение, то можете писать просто _:

Method(out int notUsed);
Method(out _);
Read More  

Надеюсь, что вы все в курсе, что операция деления выполняется намного медленнее операции умножения. Если нет, то имейте ввиду.

Я часто встречаю вот такой код:

a += b / 2f;

И мне все время хочется такой код написать так:

a += b * 0.5f;

Еще можно заменять по тому же принципу и любые другие константы. Но что делать, если у нас деление не на константу, а на x? Да все просто, делаем y = 1f / x и используем уже y.

Read More  

Условие всегда ленивое и хочет побыстрее выйти. Если v1 будет true, то что там дальше его не будет интересовать:

if (v1 == true || v2 == true) {...}

Таким образом, если у нас есть такой код:

var v1 = CalcV1();
var v2 = CalcV2();
if (v1 == true || v2 == true) {...}

Выглядит хоть и симпатично, но совершенно непроизводительно.

Лучше писать так:

if (CalcV1() == true || CalcV2() == true) {...}

Естественнно нужно понимать, что CalcV2 вызываться не будет, если CalcV1 вернет true, поэтому не нужно на это расчитывать. Но я надеюсь, что вы это знаете :)

Read More  

Мы часто пишем подобные методы:

List<int> GetItems() {
   var items = new List<int>();
   ...
   return items; 
} 

В этом методе мы просто собираем элементы и возвращаем.

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

void GetItems(List<int> items) {
   ... 
} 

Таким образом контроль над списком может быть таким:

var list = GetFromPool();
GetItems(list);
...
ReturnToPool(list);
Read More  

Допустим, у нас есть простой метод для перебора чего-либо:

class Node {
   public int value;
   public Node[] children; 
}  

Node FindRecursively(Node root, int value) {
   if (root.value == value) return root;
   for (int i = 0; i < root.children.Length; ++i) {
     var node = FindRecursively(root.children[i], value);
     if (node != null) return node;
   }
   return null; 
} 

Мы видим, что если на вход передать ноду графа и значение, то мы в итоге найдем нужную ноду, либо вернем null.

Чем плох такой вариант? Дело в том, что каждый вход в метод FindRecursively будет создавать стек под этот метод, т.е. чем больше мы используем в методе всяких переменных, тем быстрее закончится место в нашем стеке, если представить, что граф у нас довольно большой.

Что же делать?

Давайте избавимся от рекурсивного вызова метода. Самый простой вариант это сделать - использовать Stack или Queue:

Node Find(Node root, int value) {
   var queue = new Queue<Node>();
   queue.Enqueue(root);
   while (queue.Count > 0) {
     var node = queue.Dequeue();
     if (node.value == value) return node;
     for (int i = 0; i < node.children.Length; ++i) {
       queue.Enqueue(node.children[i]);
     }
   }
   return null; 
} 

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


Read More  

Допустим, что у нас есть отсортированный массив чисел:

1 2 6 8 56 234 745 998 1010

Как нам определить, что в этом массиве есть какое-то число?

Самый простой вариант - пройти линейно от первого элемента до последнего. Таким образом мы получаем O(n) (Если кто не видел пост https://t.me/unsafecsharp/97).

Но как это сделать быстрее?

На этом месте те, кто не знает, может остановиться и подумать

На самом деле существует небольшой лайфхак: если вы видите что-нибудь отсортированное (массив это или матрица - не важно) и задача - поиск, то сложность всегда будет log(n). В теории можно придумать алгоритм, который будет еще умнее, но сложность от этого не изменится.

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

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

Ищем 2:

[1 2 6 8 56 234 745 998 1010] 
[1 2 6 8 56]
[1 2 6]

Т.е. мы за 3 итерации нашли число. Вот это отбрасывание половины и есть log(n).

Read More