Система компонентов сущностей с динамической типизацией в Rust

Эта проблема

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

Я бы хотел, чтобы типы компонентов были произвольными структурами, которые могли бы содержать любой тип состояния объекта, но не знали о его поведении. Таким образом, например, объект может содержать компоненты Position, Hitbox и Velocity, но физическая подсистема может быть изменена или заменена отдельно, без необходимости изменять что-либо в этих компонентах.

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

Я новичок в Rust и проделал ограниченную работу на C ++, поэтому, возможно, я придерживаюсь неправильного подхода, и если да, то был бы признателен за совет о более эффективных способах решения этой проблемы.

На языке без строгой системы типов (и с которым я более знаком), например в JavaScript, я могу иметь массив сущностей, которые содержат набор компонентов произвольного типа, а затем выполнять проверку типов во время выполнения для получения данных. :

class Position {
  constructor(x = 0, y = 0, z = 0) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
}

class Velocity {
  constructor(x = 0, y = 0, z = 0) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
}

const world = [
  [
    Position(0, 0, 0),
    Velocity(0.25, 0.1, 1.2)
  ]
]

const physicsSystem = (world = []) => world.map((entity = []) => {
  const velocity = entity.find((component) => component instanceof Velocity)
  return velocity != null ? entity.map((component) => component instanceof Position
    ? Position(component.x + velocity.x, component.y + velocity.y, component.z + velocity.z)
    : component
  ) : component
})

window.setInterval(() => world = physicsSystem(world), 100)

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

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

Что я пробовал

Я подумал о создании хэш-карты ComponentStorage<T>, где ComponentStorage - это признак, который позволяет мне абстрагироваться от базовой структуры данных, используемой для хранения компонентов. Структура State будет содержать HashMap<ComponentStorage<std::any::TypeId, T>>. Конкретное хранилище можно было найти с помощью хэша TypeId, а затем, используя черту ComponentStorage, я мог получить Option<T> из этого хранилища по идентификатору объекта, а затем получить доступ к любым свойствам, которые содержит T.

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

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

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

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


person Raido    schedule 09.02.2020    source источник
comment
Вы слышали о спецификациях?   -  person Francis Gagné    schedule 09.02.2020
comment
@ FrancisGagné Я слышал об Amethyst, но никогда не разбирался, как они решили эту проблему. Я могу в конечном итоге просто использовать их библиотеку вместо того, чтобы изобретать колесо, но ради обучения мне все еще любопытно, как им удалось сохранить динамическую коллекцию различных хранилищ компонентов. Мне нужно провести небольшое исследование по этому поводу. Спасибо за ссылку!   -  person Raido    schedule 11.02.2020


Ответы (1)


Есть только один способ разместить объекты разных типов внутри одного контейнера: изобилие косвенных указателей.

На самом деле движок JavaScript делает за вас то же самое.

person MaxV    schedule 10.02.2020
comment
Я немного уточню свой вопрос. Да, я знаю, что мне нужна косвенная ссылка на указатель где-нибудь в моем коде, но, поскольку игра будет перебирать тысячи и миллионы компонентов за такт, кажется, что было бы лучше сохранить все компоненты один тип вместе в коллекции, таким образом они могут занимать смежную память, и тогда мне нужно только следовать указателю на коллекцию, а затем индексировать каждый компонент оттуда. Если коллекции имеют один и тот же тип, но содержат разные типы компонентов, это будет означать, что доступ к каждому компоненту будет осуществляться по указателю. - person Raido; 11.02.2020