doc arene pour slotmap
This commit is contained in:
+69
-59
@@ -1,16 +1,14 @@
|
|||||||
# Fiche Technique : Gestion des Ressources avec des Arènes Générationalles
|
# Fiche Technique : Gestion des Ressources avec des Arènes Générationalles (`slotmap`)
|
||||||
|
|
||||||
Cette fiche technique détaille l'implémentation recommandée pour gérer efficacement et en toute sécurité les ressources (maillages, textures, matériaux, lumières, etc.) au sein du moteur graphique WSG. Nous utilisons le concept d'**arène générationalle**, implémenté via la crate `thunderdome`, pour bénéficier d'IDs stables, de performances optimales et d'une protection contre les erreurs liées à la gestion de la mémoire.
|
|
||||||
|
|
||||||
Exemple avec thunderdome, mais il faut aussi évaluer la crate slotmap.
|
|
||||||
|
|
||||||
|
Cette fiche technique détaille l'implémentation recommandée pour gérer efficacement et en toute sécurité les ressources (maillages, textures, matériaux, lumières, etc.) au sein du moteur graphique WSG. Nous utilisons le concept d'**arène générationalle**, implémenté via la crate `slotmap`, pour bénéficier d'IDs stables, de performances optimales, de sécurité accrue et de fonctionnalités avancées comme les `SecondaryMap`.
|
||||||
|
|
||||||
## Objectifs
|
## Objectifs
|
||||||
|
|
||||||
* **Stabilité des IDs :** Garantir que les identifiants (Handles) des ressources restent valides même si d'autres ressources sont supprimées.
|
* **Stabilité des IDs :** Garantir que les identifiants (Keys) des ressources restent valides même si d'autres ressources sont supprimées.
|
||||||
* **Performance :** Accéder aux ressources via un ID de manière aussi rapide que possible (accès quasi direct via index).
|
* **Performance :** Accéder aux ressources via un ID de manière aussi rapide que possible (accès quasi direct via index).
|
||||||
* **Sécurité :** Empêcher l'utilisation accidentelle d'IDs obsolètes ("Dangling IDs") qui pointeraient vers des objets supprimés ou réaffectés.
|
* **Sécurité :** Empêcher l'utilisation accidentelle d'IDs obsolètes ("Dangling IDs") qui pointeraient vers des objets supprimés ou réaffectés.
|
||||||
* **Flexibilité :** Permettre l'ajout et la suppression de ressources dynamiquement, tout en encourageant la création groupée initiale pour optimiser la mémoire.
|
* **Flexibilité :** Permettre l'ajout et la suppression de ressources dynamiquement.
|
||||||
|
* **Extensibilité Future :** Profiter des fonctionnalités avancées de `slotmap` comme les `SecondaryMap` pour attacher des données dynamiques ou transitoires aux ressources existantes sans modifier leur structure principale.
|
||||||
|
|
||||||
## Concepts Clés
|
## Concepts Clés
|
||||||
|
|
||||||
@@ -18,36 +16,38 @@ Exemple avec thunderdome, mais il faut aussi évaluer la crate slotmap.
|
|||||||
|
|
||||||
Chaque type de ressource nécessite une arène séparée. Par exemple :
|
Chaque type de ressource nécessite une arène séparée. Par exemple :
|
||||||
|
|
||||||
* `MeshArena` pour stocker les `Mesh`
|
* `SlotMap<MeshId, Mesh>` pour stocker les `Mesh`
|
||||||
* `MaterialArena` pour stocker les `Material`
|
* `SlotMap<MaterialId, Material>` pour stocker les `Material`
|
||||||
* `TextureArena` pour stocker les `Texture`
|
* `SlotMap<TextureId, Texture>` pour stocker les `Texture`
|
||||||
* `LightArena` pour stocker les `Light`
|
* `SlotMap<LightId, Light>` pour stocker les `Light`
|
||||||
|
|
||||||
Cela permet d'optimiser l'accès et de garantir la cohérence des types.
|
Cela permet d'optimiser l'accès et de garantir la cohérence des types.
|
||||||
|
|
||||||
### 2. Handles (Identifiants)
|
### 2. Handles (Identifiants) Typés
|
||||||
|
|
||||||
Un Handle est un objet spécial généré par l'arène lors de l'insertion d'une ressource. Il sert de référence stable à cette ressource. Dans `thunderdome`, ce sont des types comme `thunderdome::Key`.
|
Un Handle est un objet spécial généré par l'arène lors de l'insertion d'une ressource. Il sert de référence stable à cette ressource. Nous utilisons des types personnalisés (struct wrappers) pour typer fortement ces Handles, empêchant les erreurs de mélange entre types de ressources.
|
||||||
|
|
||||||
### 3. Génération (Generation)
|
### 3. Génération (Generation)
|
||||||
|
|
||||||
Pour renforcer la sécurité, chaque Handle encapsule non seulement un **index** (où l'objet est stocké dans le tableau interne de l'arène), mais aussi un numéro de **génération**. Lorsqu'un objet est supprimé, l'emplacement dans le tableau interne est marqué comme vide, mais le numéro de génération associé à cet emplacement est incrémenté. Lorsque ce même emplacement est réutilisé pour un nouvel objet, le nouvel objet reçoit le même index mais une génération plus récente. Si un ancien Handle (avec un index et une ancienne génération) est utilisé pour tenter d'accéder à l'arène, le système vérifie si la génération du Handle correspond à celle stockée à l'index. Si ce n'est pas le cas, l'accès est refusé, empêchant l'utilisation d'un Handle périmé.
|
Pour renforcer la sécurité, chaque Handle encapsule non seulement un **index** (où l'objet est stocké dans le tableau interne de l'arène), mais aussi un numéro de **génération**. Lorsqu'un objet est supprimé, l'emplacement dans le tableau interne est marqué comme vide, mais le numéro de génération associé à cet emplacement est incrémenté. Lorsque ce même emplacement est réutilisé pour un nouvel objet, le nouvel objet reçoit le même index mais une génération plus récente. Si un ancien Handle (avec un index et une ancienne génération) est utilisé pour tenter d'accéder à l'arène, le système vérifie si la génération du Handle correspond à celle stockée à l'index. Si ce n'est pas le cas, l'accès est refusé, empêchant l'utilisation d'un Handle périmé.
|
||||||
|
|
||||||
## Implémentation avec `thunderdome`
|
## Implémentation avec `slotmap`
|
||||||
|
|
||||||
### Dépendance
|
### Dépendance
|
||||||
|
|
||||||
Ajoutez `thunderdome` à votre `Cargo.toml` :
|
Ajoutez `slotmap` à votre `Cargo.toml` :
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thunderdome = "0.5" # Remplacez par la dernière version stable
|
slotmap = { version = "1.0", features = ["serde"] } # Inclure 'serde' si nécessaire, sinon omettre la feature
|
||||||
```
|
```
|
||||||
|
|
||||||
Structure de Base
|
(Note : slotmap a une dépendance sur serde par défaut. Si vous n'avez absolument pas besoin de sérialisation/désérialisation des arènes, vous pouvez potentiellement chercher une alternative légère comme thunderdome, mais slotmap est le standard et offre plus de fonctionnalités).
|
||||||
|
|
||||||
```rust
|
|
||||||
use thunderdome::{Arena, Index};
|
## Structure de Base et Typage Fort
|
||||||
|
|
||||||
|
use slotmap::{SlotMap, new_key_type};
|
||||||
|
|
||||||
// --- Définition des types de ressources ---
|
// --- Définition des types de ressources ---
|
||||||
// Ces structs doivent être définies ailleurs dans votre code
|
// Ces structs doivent être définies ailleurs dans votre code
|
||||||
@@ -71,12 +71,19 @@ pub struct Light {
|
|||||||
// ... champs de la lumière ...
|
// ... champs de la lumière ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Définition des Handles typés ---
|
||||||
|
// Ces lignes créent des types uniques pour chaque Handle
|
||||||
|
new_key_type! { pub struct MeshId; }
|
||||||
|
new_key_type! { pub struct MaterialId; }
|
||||||
|
new_key_type! { pub struct TextureId; }
|
||||||
|
new_key_type! { pub struct LightId; }
|
||||||
|
|
||||||
// --- Définition des arènes ---
|
// --- Définition des arènes ---
|
||||||
pub struct ResourceManager {
|
pub struct ResourceManager {
|
||||||
meshes: Arena<Mesh>,
|
meshes: SlotMap<MeshId, Mesh>,
|
||||||
materials: Arena<Material>,
|
materials: SlotMap<MaterialId, Material>,
|
||||||
textures: Arena<Texture>,
|
textures: SlotMap<TextureId, Texture>,
|
||||||
lights: Arena<Light>,
|
lights: SlotMap<LightId, Light>,
|
||||||
// Ajoutez d'autres arènes pour d'autres types si nécessaire
|
// Ajoutez d'autres arènes pour d'autres types si nécessaire
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,114 +91,117 @@ impl ResourceManager {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// Optionnel : spécifier une capacité initiale estimée pour chaque arène
|
// Optionnel : spécifier une capacité initiale estimée pour chaque arène
|
||||||
// let estimated_mesh_count = 100;
|
// let estimated_mesh_count = 100;
|
||||||
// let meshes = Arena::with_capacity(estimated_mesh_count);
|
// let meshes = SlotMap::with_capacity_and_key(estimated_mesh_count);
|
||||||
// ...
|
// ...
|
||||||
Self {
|
Self {
|
||||||
meshes: Arena::new(),
|
meshes: SlotMap::new(),
|
||||||
materials: Arena::new(),
|
materials: SlotMap::new(),
|
||||||
textures: Arena::new(),
|
textures: SlotMap::new(),
|
||||||
lights: Arena::new(),
|
lights: SlotMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Méthodes pour ajouter des ressources ---
|
// --- Méthodes pour ajouter des ressources ---
|
||||||
pub fn add_mesh(&mut self, mesh: Mesh) -> Index { // Retourne un Handle (Index)
|
pub fn add_mesh(&mut self, mesh: Mesh) -> MeshId { // Retourne un Handle typé
|
||||||
self.meshes.insert(mesh)
|
self.meshes.insert(mesh)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_material(&mut self, material: Material) -> Index {
|
pub fn add_material(&mut self, material: Material) -> MaterialId {
|
||||||
self.materials.insert(material)
|
self.materials.insert(material)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_texture(&mut self, texture: Texture) -> Index {
|
pub fn add_texture(&mut self, texture: Texture) -> TextureId {
|
||||||
self.textures.insert(texture)
|
self.textures.insert(texture)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_light(&mut self, light: Light) -> Index {
|
pub fn add_light(&mut self, light: Light) -> LightId {
|
||||||
self.lights.insert(light)
|
self.lights.insert(light)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Méthodes pour accéder aux ressources ---
|
// --- Méthodes pour accéder aux ressources ---
|
||||||
pub fn get_mesh(&self, handle: Index) -> Option<&Mesh> {
|
pub fn get_mesh(&self, handle: MeshId) -> Option<&Mesh> {
|
||||||
self.meshes.get(handle)
|
self.meshes.get(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_material(&self, handle: Index) -> Option<&Material> {
|
pub fn get_material(&self, handle: MaterialId) -> Option<&Material> {
|
||||||
self.materials.get(handle)
|
self.materials.get(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_texture(&self, handle: Index) -> Option<&Texture> {
|
pub fn get_texture(&self, handle: TextureId) -> Option<&Texture> {
|
||||||
self.textures.get(handle)
|
self.textures.get(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_light(&self, handle: Index) -> Option<&Light> {
|
pub fn get_light(&self, handle: LightId) -> Option<&Light> {
|
||||||
self.lights.get(handle)
|
self.lights.get(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Méthodes pour accéder aux ressources mutables (utile dans update(), mais à éviter pendant le rendu) ---
|
// --- Méthodes pour accéder aux ressources mutables (utile dans update(), mais à éviter pendant le rendu) ---
|
||||||
pub fn get_mesh_mut(&mut self, handle: Index) -> Option<&mut Mesh> {
|
pub fn get_mesh_mut(&mut self, handle: MeshId) -> Option<&mut Mesh> {
|
||||||
self.meshes.get_mut(handle)
|
self.meshes.get_mut(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_material_mut(&mut self, handle: Index) -> Option<&mut Material> {
|
pub fn get_material_mut(&mut self, handle: MaterialId) -> Option<&mut Material> {
|
||||||
self.materials.get_mut(handle)
|
self.materials.get_mut(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_texture_mut(&mut self, handle: Index) -> Option<&mut Texture> {
|
pub fn get_texture_mut(&mut self, handle: TextureId) -> Option<&mut Texture> {
|
||||||
self.textures.get_mut(handle)
|
self.textures.get_mut(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_light_mut(&mut self, handle: Index) -> Option<&mut Light> {
|
pub fn get_light_mut(&mut self, handle: LightId) -> Option<&mut Light> {
|
||||||
self.lights.get_mut(handle)
|
self.lights.get_mut(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Méthodes pour supprimer des ressources ---
|
// --- Méthodes pour supprimer des ressources ---
|
||||||
pub fn remove_mesh(&mut self, handle: Index) -> Option<Mesh> { // Option<T> retourné est la ressource supprimée
|
pub fn remove_mesh(&mut self, handle: MeshId) -> Option<Mesh> { // Option<T> retourné est la ressource supprimée
|
||||||
self.meshes.remove(handle)
|
self.meshes.remove(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_material(&mut self, handle: Index) -> Option<Material> {
|
pub fn remove_material(&mut self, handle: MaterialId) -> Option<Material> {
|
||||||
self.materials.remove(handle)
|
self.materials.remove(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_texture(&mut self, handle: Index) -> Option<Texture> {
|
pub fn remove_texture(&mut self, handle: TextureId) -> Option<Texture> {
|
||||||
self.textures.remove(handle)
|
self.textures.remove(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_light(&mut self, handle: Index) -> Option<Light> {
|
pub fn remove_light(&mut self, handle: LightId) -> Option<Light> {
|
||||||
self.lights.remove(handle)
|
self.lights.remove(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Méthode pour vérifier si un Handle est toujours valide ---
|
// --- Méthode pour vérifier si un Handle est toujours valide ---
|
||||||
pub fn contains_mesh(&self, handle: Index) -> bool {
|
pub fn contains_mesh(&self, handle: MeshId) -> bool {
|
||||||
self.meshes.contains_key(handle)
|
self.meshes.contains_key(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_material(&self, handle: Index) -> bool {
|
pub fn contains_material(&self, handle: MaterialId) -> bool {
|
||||||
self.materials.contains_key(handle)
|
self.materials.contains_key(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_texture(&self, handle: Index) -> bool {
|
pub fn contains_texture(&self, handle: TextureId) -> bool {
|
||||||
self.textures.contains_key(handle)
|
self.textures.contains_key(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_light(&self, handle: Index) -> bool {
|
pub fn contains_light(&self, handle: LightId) -> bool {
|
||||||
self.lights.contains_key(handle)
|
self.lights.contains_key(handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
# Bonnes Pratiques d'Utilisation
|
||||||
|
|
||||||
## Bonnes Pratiques d'Utilisation
|
1. Initialisation Groupée : Encouragez les utilisateurs de WSG à créer la majorité de leurs ressources statiques (maillages de niveau, matériaux de base, textures fixes, lumières ambiantes, etc.) avant de lancer la boucle de rendu principale. Vous pouvez éventuellement fournir une fonction reserve_initial_capacities(&mut resource_manager, expected_counts...) qui appelle SlotMap::reserve() pour optimiser la mémoire initiale.
|
||||||
|
2. Stocker les Handles Typés : Les entités de la scène (ou les objets graphiques) doivent stocker les Handles typés (MeshId, MaterialId, etc.) retournés lors de l'ajout des ressources. Par exemple, un objet GameObject pourrait contenir un Option<MeshId> pour son Mesh, un Option<MaterialId> pour son Material, etc. Le typage fort empêche les erreurs de mélange.
|
||||||
1. Initialisation Groupée : Encouragez les utilisateurs de WSG à créer la majorité de leurs ressources statiques (maillages de niveau, matériaux de base, textures fixes, lumières ambiantes, etc.) avant de lancer la boucle de rendu principale. Vous pouvez éventuellement fournir une fonction reserve_initial_capacities(&mut resource_manager, expected_counts...) qui appelle Arena::reserve() pour optimiser la mémoire initiale.
|
3. Accès pendant le rendu : Pendant la phase de rendu (render()), accédez aux ressources via les Handles typés stockés. Utilisez get() (lecture seule) pour éviter les conflits avec les systèmes de mise à jour concurrents.
|
||||||
2. Stocker les Handles : Les entités de la scène (ou les objets graphiques) doivent stocker les Index (Handles) retournés lors de l'ajout des ressources. Par exemple, un objet GameObject pourrait contenir un Option<Index> pour son Mesh, un Option<Index> pour son Material, etc.
|
|
||||||
3. Accès pendant le rendu : Pendant la phase de rendu (render()), accédez aux ressources via les Handles stockés. Utilisez get() (lecture seule) pour éviter les conflits avec les systèmes de mise à jour concurrents.
|
|
||||||
4. Accès pendant la mise à jour : Pendant la phase de mise à jour (update()), vous pouvez utiliser get_mut() si des modifications sont nécessaires. Soyez vigilant à la gestion des lifetimes et de la mutabilité.
|
4. Accès pendant la mise à jour : Pendant la phase de mise à jour (update()), vous pouvez utiliser get_mut() si des modifications sont nécessaires. Soyez vigilant à la gestion des lifetimes et de la mutabilité.
|
||||||
5. Validation : Avant d'utiliser un Handle potentiellement ancien ou incertain, vérifiez sa validité avec contains_* si l'opération n'est pas critique, ou laissez get() renvoyer None si le Handle est invalide.
|
5. Validation : Avant d'utiliser un Handle potentiellement ancien ou incertain, vérifiez sa validité avec contains_* si l'opération n'est pas critique, ou laissez get() renvoyer None si le Handle est invalide.
|
||||||
6. Suppression Dynamique : Bien que possible, la suppression de ressources pendant la boucle de rendu doit être faite avec prudence. Assurez-vous que les entités ou objets qui référençaient cette ressource soient informés ou nettoyés pour éviter d'utiliser des Handles invalides. La suppression est souvent mieux gérée en fin de frame ou via un système de "marquage pour suppression" suivi d'un nettoyage différé.
|
6. Suppression Dynamique : Bien que possible, la suppression de ressources pendant la boucle de rendu doit être faite avec prudence. Assurez-vous que les entités ou objets qui référençaient cette ressource soient informés ou nettoyés pour éviter d'utiliser des Handles invalides. La suppression est souvent mieux gérée en fin de frame ou via un système de "marquage pour suppression" suivi d'un nettoyage différé.
|
||||||
|
7. Futur : SecondaryMaps : slotmap permet d'utiliser des SecondaryMap pour associer dynamiquement des données à des ressources existantes sans modifier leur structure principale. Par exemple, SecondaryMap<MeshId, Transform> pourrait stocker les transformations actuelles de chaque maillage. Cela peut être utile pour le rendu ou pour des systèmes de physique/transformation indépendants.
|
||||||
|
|
||||||
## Avantages de cette Approche
|
# Avantages de cette Approche
|
||||||
Simplicité d'utilisation : Les développeurs utilisent des Handles stables, sans se soucier des références Rust ou des lifetimes complexes pour les ressources partagées.
|
|
||||||
Performance : Les accès sont rapides, proches de l'accès direct via index, grâce à l'implémentation interne de thunderdome.
|
* Simplicité d'utilisation : Les développeurs utilisent des Handles typés stables, sans se soucier des références Rust ou des lifetimes complexes pour les ressources partagées.
|
||||||
Sécurité : Le système de génération empêche efficacement l'utilisation de Handles invalides, ce qui peut causer des plantages ou des bugs subtils.
|
* Performance : Les accès sont rapides, proches de l'accès direct via index, grâce à l'implémentation interne de slotmap.
|
||||||
Conformité avec Rust : Respecte les principes de propriété et de sécurité mémoire de Rust sans recourir à Rc<RefCell<T>> ou d'autres constructions potentiellement coûteuses ou moins sûres pour la gestion partagée des ressources.
|
* Sécurité : Le système de génération empêche efficacement l'utilisation de Handles invalides, ce qui peut causer des plantages ou des bugs subtils.
|
||||||
|
* Conformité avec Rust : Respecte les principes de propriété et de sécurité mémoire de Rust sans recourir à Rc<RefCell<T>> ou d'autres constructions potentiellement coûteuses ou moins sûres pour la gestion partagée des ressources.
|
||||||
|
* Typage Fort : Les types MeshId, MaterialId, etc., empêchent les erreurs de compilation liées au mélange de Handles de types différents.
|
||||||
|
* Extensibilité : L'écosystème slotmap (SecondaryMap) offre des perspectives pour des architectures plus complexes à l'avenir.
|
||||||
|
|||||||
Reference in New Issue
Block a user