Конечно же при смене.Хранить максимально посчитанную таблицу параметров чара, обновляя ее при смене эквипа/бафов/обнормалов, или пересчитывать каждый тик/действие?
- При добавлении эффекта к сущности - в соответствующие калькуляторы добавляются функции саб-эффектов этого эффекта.
- При снятии эффекта с сущности - из соответствующих калькуляторов удаляются функции саб-эффектов этого эффекта.
Овербаф получается при абьюзе мозга разработчика, который не посчитал нужным сделать нормально и упорол костыль.я правильно понимаю, что из-за абьюза именно этой логики получался овербаф? и он идет через наслоение новых баффов в обход проверки на уже имеющийся такой, или через ошибку стирания старого ?
И я не совсем одуплил из твоего объявнения. при каждом обращении к параметрам чара при изменении состояния или атаке идет пересчет всех функций в калькуляторе (всех статов), или калькулятор посчитал нынешнее состояние чара, создал массив результатов, и пока нет изменения их - он берет значения из этого массива?
Никакие результаты никуда не сохраняются. Все расчеты только в реальном времени и на каждое действие. Буквально на каждое. Т.к там расчет представляет из себя цепочку простейших арифметических действий из набора функций внутри калькулятора.И я не совсем одуплил из твоего объявнения. при каждом обращении к параметрам чара при изменении состояния или атаке идет пересчет всех функций в калькуляторе (всех статов), или калькулятор посчитал нынешнее состояние чара, создал массив результатов, и пока нет изменения их - он берет значения из этого массива?
Никакие результаты никуда не сохраняются. Все расчеты только в реальном времени и на каждое действие. Буквально на каждое. Т.к там расчет представляет из себя цепочку простейших арифметических действий из набора функций внутри калькулятора.
Все проблемы производительности упираются в синхронизацию. Ты не можешь сохранять значения, не синхронизировав этот процесс для множества потоков. Следовательно прочитать это значение ты тоже можешь только из под синхронизированного участка(что логично). Поэтому любое сохранение результата влечет просто дичайшее удорожание процесса расчета. Не стоит недооценивать современные процессоры. Для них простые операции, которые не долбятся постоянно в барьерные инструкции, практически бесплатны(на фоне общего количества вычислений на фоне)
Да, я про это писал выше. В функциях калькулятора не должно быть какой-то сложной логики, любого вида ожиданий и синхронизаций. Т.е например, если функция, которая считает скорость 20 раз в секунду, полезет в базу данных, чтобы проверить, есть ли у твоего клана пассивка на +1 Dex, то это ничем хорошим не кончится.спасибо, про барьерные инструкции сам погуглю )
но тогда выходит для оптимизации и снижения нагрузки стоит упрощать формулы рассчета, и сокращать их параметры
Да, я про это писал выше. В функциях калькулятора не должно быть какой-то сложной логики, любого вида ожиданий и синхронизаций. Т.е например, если функция, которая считает скорость 20 раз в секунду, полезет в базу данных, чтобы проверить, есть ли у твоего клана пассивка на +1 Dex, то это ничем хорошим не кончится.
Как правило, информация о всех клановых скиллах хранится в объекте клана, в памяти. Поэтому доступ к этим данным почти мгновенный. Я просто привел пример некорректной реализации.а как тогда реализовываются клан-скилы? они прописаны изначально в хранилище калькуляторов как инфа о чаре?
package ru.nts.benchmarks;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class CalculatorBenchmark {
private Calculator calculator;
private final Random random = new Random();
private final double initValue = random.nextDouble();
@Param({"100000", "1000000", "10000000"})
public int operations;
@Setup
public void setup() {
Func[] funcs = new Func[10];
for (int i = 0; i < 5; i++) {
funcs[i] = new ADD(random.nextInt(10), random.nextDouble() * 100);
funcs[i + 5] = new MUL(random.nextInt(10), random.nextDouble() * 10);
}
calculator = new Calculator(funcs);
}
@Benchmark
public double operations_avg() {
double result = 0;
for (int i = 0; i < operations; i++) {
result = calculator.calc(initValue);
}
return result;
}
public static void main(String[] args) throws RunnerException {
new Runner(new OptionsBuilder().include(CalculatorBenchmark.class.getSimpleName()).build()).run();
}
public interface Func { double calc(double init); int order(); }
record ADD(int order, double value) implements Func {@Override public double calc(double init) { return init + value; }}
record MUL(int order, double value) implements Func {@Override public double calc(double init) { return init * value; }}
private record Calculator(Func[] funcs) {
private Calculator(Func[] funcs) {
this.funcs = Arrays.stream(funcs).sorted(Comparator.comparingInt(Func::order)).toArray(Func[]::new);
}
public double calc(double init) {
double result = init;
for (Func func : funcs) {
result = func.calc(result);
}
return result;
}
}
}
Benchmark (operations) Mode Cnt Score Error Units
CalculatorBenchmark.operations_avg 100000 avgt 5 1,409 ± 0,007 ms/op
CalculatorBenchmark.operations_avg 1000000 avgt 5 13,899 ± 0,025 ms/op
CalculatorBenchmark.operations_avg 10000000 avgt 5 142,827 ± 1,391 ms/op
Из того что я видел обычно вешается бафф или же просто эффект на персонажа, т.е. добавляются формулы различных типов. Вот например добавление таких формул и является примером правил для пересчета. Я в принципе согласен и сделал ужe cache в своей разработке. Не нужно пересчитывать то что можно сохранить. Но главное в том что пересчитываться должно только в определенных правилах а не всегда. Ну а правил... или же ситуаций, не так уж много.Насчет кэширования значений - не стоит еще забывать еще об одной причине того, почему стоит делать перерасчеты каждый раз когда идет обращение к стате - у стат, в конкретном значении могут быть кондишны, которые влияют на использование этого значения в данный момент. Например значение учитывается в расчете только при нахождении в определенной зоне, при уровне хп не выше определенного и т.д. и т.п.
Вот с такими кондишнами в целом и надо достаточно вменяемо работать, чтобы были достаточно простыми и отрабатывали проверки как можно быстрее и оптимальней, а то бывает в них такого нагородят, что только за голову хватаешься - в какой-то из старых сборок видел даже работу с бд в них...
val baseValue = 123.0
var finalValue = baseValue
finalValue *= 1.3
println(finalValue)
finalValue /= 1.3
println(finalValue)
Забыл оветить раньше. Проблема ведь в том что в клиент пересылается намного меньше значений чем есть видов калькуляторов. Да и в ядре при каждом действии персонажа будут запрашиваться различные пересчеты калькуляторов. Поэтому не имеет смысла кешировать только данные для клиента или когда данные пересылаются к клиенту. Или же можно только остановится на определенных значениях и кешировать только такие. Но смысл ведь в том чтобы просто не пересчитывать значения, вот кеширование и помогает.Кешировать имеет смысл только уже вычисленные итоговые значения, которые отправляются в клиент, для того, чтобы триггерить изменения централизовано и использовать кумулятивные обновления. Такое проще отслеживать и в этом есть определенный смысл.
Таких пакетах как UserInfo, PetInfo и NpcInfo. Ну потом есть также StatusUpdate где пересылаются больше статов чем в других пакетах.Соглашусь что представленный вариант самый простой + эффективный.
Мой ранее предложенный вариант можно назвать инкрементальным подсчетом, потому что он условно предполагался для
но в реальном мире его достаточно сложно (невозможно) применить так, чтобы его преимущества (относительно варианта выше) принесли пользу сопоставимую с трудозатратами.Код:val baseValue = 123.0 var finalValue = baseValue finalValue *= 1.3 println(finalValue) finalValue /= 1.3 println(finalValue)
Одна из причин - динамические значения модификаторов. Тут нужно гарантировать что модификатор при удалении будет равнозначен тому, который был использован при добавлении. А как такое гарантировать без хранения списка модификаторов
Возник концептуальный вопрос по калькулятору с пересчетом на каждый гет, как формируются блоки с значениями статов в динамических пакетах и на каком уровне?
вот в такой реализации применения/убирания значений есть одна большая проблема - float/double всегда имеют определенную погрешность, начиная с определенных цифр после запятой и в итоге вроде как после умножения и обратного деления на одно и то же значение мы должны получить оригинальное значение, но в реальности оно будет уже незначительно отличаться и эта погрешность будет накапливаться при каждом применении/убирании значений.Код:val baseValue = 123.0 var finalValue = baseValue finalValue *= 1.3 println(finalValue) finalValue /= 1.3 println(finalValue)
вот в такой реализации применения/убирания значений есть одна большая проблема - float/double всегда имеют определенную погрешность, начиная с определенных цифр после запятой и в итоге вроде как после умножения и обратного деления на одно и то же значение мы должны получить оригинальное значение, но в реальности оно будет уже незначительно отличаться и эта погрешность будет накапливаться при каждом применении/убирании значений.
Вроде как и мелочь, но в итоге например постоянным ребаффом одних и тех же баффов/дебаффов так разогнать или наоборот значительно уменьшить какую нибудь стату которая так вычисляется.
Сам с таким столкнулся в одной из сборок, которую просили поковырять и где сделали работу с трайтами с закосом под птс эффектами p_attack_trait/p_defence_trait, в которых как раз по такому принципу вот это все и считалось и легко можно было разогнать таким макаром за счет погрешностей разные резисты...
З.Ы. можно конечно для точности в подобном юзать BigDecimal вместо float/double, но это как из пушки по воробьям...
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?