Patterns of Programming

Статьи в секции:

  • Newtype

    Newtype Pattern

  • SRP

    Single Responsibility Principle

Struct of Arrays (SoA)

Если нужно работать с штучним объектом, вдобавок менять у него сразу много полей, то лучше SoA. Use case: RPG игра (тыкаешь в одного персонажа), БД (достаешь одну запись), редактор (меняешь один объект):

// Работа с ОДНИМ объектом
fn feed_oldest_animal(animals: &mut [Animal]) {
    let oldest = &mut animals[0];  // берем 1 животное
    oldest.health += 10.0;         // работаем со всеми полями сразу
    oldest.hunger -= 5.0;
}

ECS: Data-Oriented Design

Сложно поддерживать код SoA. На помощь в Rust приходят ECS (Entity Component System). Примеры - Bevy ECS, Hecs, Legion. ECS абстрагирует работу с памятью. Вы пишете компоненты как маленькие структуры:

struct Position { x: f32, y: f32 }
struct Velocity { dx: f32, dy: f32 }

А затем пишете Системы (функции), которые запрашивают только то, что им нужно:

// Пример из Bevy ECS
fn physics_system(mut query: Query<(&mut Position, &Velocity)>) {
    for (mut pos, vel) in query.iter_mut() {
        pos.x += vel.dx;
        pos.y += vel.dy;
    }
}

ECS-фреймворк сам раскладывает компоненты в памяти в виде плотных массивов (почти как SoA, чаще всего используя паттерн Archetypes). Когда physics_system запрашивает данные, то бежит по памяти линейно, идеально утилизируя кэш.

Array of Structs (AoS)

Если надо сделать массовую операцию, то лучше AoS. Use cases: 3D-графика (двигаем 10,000 частиц), нейросети (матричные операции), аудиообработка (тысячи сэмплов):

// Обновляем всех сразу
fn update_all_positions(particles: &mut Particles) {
    // Векторизация! +SIMD ускоряет +х10 скорости
    for i in 0..particles.positions.len() {
        particles.positions[i].x += particles.velocities[i].x;
    }
}

“Hybrid” Arrays (AoSoA)

А есть вариант менять объекты подмножествами. То есть по несколько штук, но не все. Тогда гибридный подход AoSoA. Use case: комп игры, 3D-графика (эмиттеры частиц):

// Группируем по 8 частиц в "пачку"
struct ParticlePack {
    x: [f32; 8],   // 8 позиций X
    y: [f32; 8],   // 8 позиций Y
    vx: [f32; 8],  // 8 скоростей X
}
let mut game: Vec<ParticlePack>; // массив пачек
  • Можно удалять/добавлять пачками (удобно);
  • Внутри пачки — SoA для скорости;
  • Размер пачки = размер кэш-линии (64 байта).