Je viens vers vous car j'ai un souci concernant la mise en place d'une signature composant via un bitset<64> dans un ECS. Ici le problème ne concerne pas les opérations à effectuer sur les bits (ajouter/supprimer/comparer), mais plutôt de savoir où stocker les signatures ?
Dans l'idée, voilà ce que je pense faire :
Au lieu d'utiliser un simple type pour Entity (actuellement size_t), créer une class/struct Entity contenant un ID et un bitset correspondant aux composants qui la compose
Chaque struct de composant doit contenir son propre bitset constant permettant de l'identifier
Les différents systèmes (pas encore codé pour le moment) analyseront le bitset d'une Entity pour savoir si elle doit être prise en compte
J'ai aussi une classe template Components représentant un simple vector,permettant le maintiens de tous les composants d'un même type ensemble. Cette classe a pour mission de créer des composants en leur associant l'ID de l'Entity correspondante.
Maintenant, j'avoue que je suis un peu dans le brouillard concernant l'organisation de ces signatures de bits, si jamais quelqu'un a une idée sur la quetion je suis preneur .
En fait, l'idée de la clé, c'est que chaque bit représente la présence (ou l'absence) d'un composant particulier qui est rattaché à une entité bien particulière.
Tu peux, effectivement, envisager de faire en sorte que chaque entité soit représentée par une paire de donnée composée de son identifiant (un size_t ) et du bitset représentant les composants qui y sont rattachés.
Il y a cependant un aspect à prendre en compte, si tu crées une structure qui regroupe les deux:
la... notion d'entité que tu obtiens doit forcément se retrouver avec... une sémantique d'entité, avec tout ce que cela comporte d'interdiction de la copie et de l'assignation, vu que tu ne voudrais en aucun cas te retrouver dans une situation dans laquelle, à un instant de l'exécution T, tu te retrouverais avec... deux données représentant la même entité (et tu voudrais encore moins que la clé associée à chacune de ces deux entités "identiques" soit différente)
Tu me diras que, de toutes facons, tes clés ne devront jamais être copiées ni assignées, pour, justement, éviter le risque de se retrouver avec deux clés associées à une entité identique mais présentant des valeurs différentes, et que l'on n'a donc pas grand chose à perdre à créer cette structure.
La seule chose, c'est qu'il faut également éviter autant que possible toute allocation dynamique de la mémoire, au moment de créer l'entité. Parce que c'est la merde à gérer, et parce que cela demande beaucoup de temps d'y avoir recours.
Mais, il n'y a rien non plus qui t'empêche d'avoir, d'un coté, un EntityHolder, qui se contente de maintenir la liste des entités qui existent, et de l'autre un KeyHolder qui se contente de maintenir à jour la liste des clés pour chacune de ces entités ;) Pour autant que les clés soient systématiquement créées et détruites en même temps que les entités (et que les logiques d'ajout / de suppression soient identiques pour les deux listes), il ne devrait pas être *** trop difficile *** de maintenir ces deux listes cohérentes et synchronisées; même si le travail serait sans doute plus facile en n'ayant qu'une seule liste
Pour ce qui est de l'association des différents bits de ta clé aux différents types de composant, l'idée est de disposer d'un système qui te permette de faire le "mapping" type de composant <--> index du bit correspondant "assez facilement".
Une "simple" map proche de
std::map<std::type_index, size_t> mapper;
(où la clé correspond au type_index de chaque composant et où le size_t correspond à l'indice du bit équivalent dans la clé) associée à une fonction permettant de récupérer l'indice du bit dans la clé en fonction du type de composant recherché devrait pouvoir faire l'affaire. En ajoutant un petit helper du genre de
tu auras tout ce qu'il faut pour pouvoir vérifier si la "clé" d'une entité particulière correspond à "l'emprunte" qu'elle doit présenter pour que l'entité soit utilisable par un système particulier, qui a besoin de la présence de composant bien particulier pour pouvoir fonctionner.
La seule chose (et sans doute la plus difficile à mettre au point), c'est que chaque composant devra être enregistré auprès de ce "mapper". Ah, et j'oubliais: chaque composant doit être unique. Pas de bol, si tu as deux alias de type, par exemple
using PV = size_t;
using MP = size_t;
tu risque d'être mal barre, car, tels quels PV et MP typeid(MP) et typeid(PV) seront tous les deux égaux à... typeid(size_t)... Ce qui n'est pas l'idéal :P.
Tu devras donc trouver un moyen pour lever l'ambiguité, par exemple, avec l'aide d'un tag supplémentaire, sous une forme proche de
template <typename T, // Le type utilisé comme valeur du composant
typename TAG, // un tag de levée d'ambiguité
>
struct ComponentType{
Id id; // l'identifiant de l'entité auquel est rattaché
// le composant
T value; // la valeur associée à ce composant
};
struct mpTag{};
struct pvTag{};
using MPComponent = ComponentType<size_t, mpTag>;
using LiveComponent = ComponentType<size_t, pvTag>;
De cette manière typeid(MPComponent) et typeid(LiveComponent) seront bel et bien différents
- Edité par koala01 12 mai 2018 à 17:18:04
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
la... notion d'entité que tu obtiens doit forcément se retrouver avec... une sémantique d'entité
Effectivement .
koala01 a écrit:
La seule chose, c'est qu'il faut également éviter autant que possible toute allocation dynamique de la mémoire, au moment de créer l'entité.
Ça marche. On va éviter de se créer des problèmes inutilement alors . Bon ça va, en règle générale j'ai tendance à les éviter, sauf obligations ou cas particuliers.
koala01 a écrit:
Mais, il n'y a rien non plus qui t'empêche d'avoir, d'un coté, un EntityHolder, qui se contente de maintenir la liste des entités qui existent, et de l'autre un KeyHolder qui se contente de maintenir à jour la liste des clés pour chacune de ces entités
J'avoue que l'idée est plaisante et pas difficile à mettre en place, je note !
koala01 a écrit:
Pour ce qui est de l'association des différents bits de ta clé aux différents types de composant, l'idée est de disposer d'un système qui te permette de faire le "mapping" type de composant <--> index du bit correspondant "assez facilement".
Cela m'avait déjà traversé l'esprit, mais pas pensé à l'ambiguïté possible ! Je vais me pencher sur cette idée et essayer de mettre ça en place. En attendant, je vais en profiter pour réviser un peu les variadics que j'ai très peu exploré .
Bon et bien il y a du pain sur la planche ! Un gros merci pour toutes ces explications .
Un petit détail aussi, la liste des composants qui forment une entité peut varier dans le temps. Prenons par exemple un RPG 3D et considérons le cas d'une épée:
L'épée se trouve sur le sol, elle fait partie du décor, elle possède un composant position qui sera utilisé par le moteur de rendu graphique pour la dessiner.
L'épée se trouve dans l'inventaire du joueur, elle ne possède pas de composant position.
L'épée se trouve dans la main du joueur, elle ne possède toujours pas de composant position, mais un une information pour dire que c'est l'arme que le joueur a en main
Bien sùr le joueur peut changer d'arme (transaction avec l'inventaire), il peut jeter cette épée ou bien la prendre sur le sol, lors d'un combat, il peut être désarmé. On peut aussi imaginer des trucs plus subtils comme par exemple, l'assassin fourbe qui aura enduit son épée de poison, le magicien qui l'enchante ... Comment traduire tout ça ?
Lorsque tu rajoute un composant (la position, par exemple), tu crées un composant "position" associé à l'identifiant de l'épée, et tu mets à 1 le bit qui correspond au composant "position" dans la clé
Quand elle est ramassée (et mise dans l'inventaire), tu détruit le composant "position" associé à l'identifiant de l'épée, et tu mets à 0 le bit correspondant à ce composant dans la clé.
Parallèlement à cela, tu crée un composant "dans l'inventaire" qui est associé à cette épée, et tu met à 1 le bit qui correspond à ce composant dans la clé.
Quand tu décide d'équiper ton personnage de l'épée, tu détruit le composant "dans l'inventaire" qui est associé à cette épée, et tu remet à 0 le bit correspond à ce composant.
Et, parallèlement, tu crées un composant "dans la main" associé à l'épée mets à 1 le bit qui correspond au composant "dans la main", et ainsi de suite...
L'idée est vraiment toute simple finalement : chaque fois que tu associe un type de composant à une entité, tu met le bit correspondant à ce composant (pour la clé correspondant à l'entité concernée) à 1.
Chaque fois que tu détruit un type de composant (associé à une entité), tu met le bit correspondant à ce composant (pour la clé correspondant à l'entité concernée) à 0.
Chaque fois que tu veux savoir si un type de composant est associé à une entité particulière -- pour éviter le temps de recherche du composant, qui peut être coûteux surtout s'il n'existe pas -- tu commences par vérifier la clé associée à cette entité particulière, pour savoir si elle dispose ou on de ce composant.
Le reste, c'est un principe de masque "classique", car, si tu veux -- par exemple -- les armes qui ont la capacité de provoquer des dégats tranchants et des dégats magique, ce sera comme si tu avais un code proche de
/* je n'aime pas les define, mais c'est pour bien te montrer */
#define SLASH_DAMAGE 1
#define MAGIC_DAMAGE 2
#define BLUNT_DAMAGE 4
#define MAGIC_POINT 8
et tu pourras donc faire une vérification du genre de
footPrint = SLASH_DAMAGE | MAGIC_DAMAGE;
if(key & footPrint == footPrint){
/* il y a des dégats magiques et des dégats coupants */
}
footprint2 = MAGIC_POINT | MAGIC_DAMAGE;
if(key & footPrint2 == footPrint2){
/* il y a des dégats magiques, et une utilisation de point de magie
*/
}
La seule différence, ici, c'est que l'on n'utilise pas les define d'une part et un int de l'autre pour arriver à notre but, mais un système "centralisé" qui définit l'indice des bits pour chaque composant dans un bitset d'une part et... un bitset de l'autre
- Edité par koala01 13 mai 2018 à 1:50:44
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
On comprends bien en voyant l'output que la version variadic est d'abord appelée 3 fois, puis pour le dernier élément, c'est la version 1 argument qui est appelée :
Maintenant, j'ai testé un peu le FootprinterCreator et pas de souci il fait son boulot. Le truc c'est que le fonctionnement m'échappe complètement et en voyant l'output je suis encore plus paumé :
D'accord ! Moi qui pensais que tout était toujours évalué dans le même sens (droite vers gauche)... Donc, sauf dans le cas de certains opérateurs ou indiqué explicitement entre parenthèse, le compilo' fait ce qu'il veut. C'est noté, du coup je vais aussi mettre wandbox de côté, c'est bien sympa pour tester.
Ah c'est bien pratique __PRETTY_FUNCTION__ ! C'est plus clair maintenant.
Ceci dit, tu te fous pas mal de l'ordre dans lequel les paramètres templates seront définis dans le cas présent, vu que chaque paramètre template sera associé à un bit particulier de ta clé...
que le calcul soit effectué sous la forme de
00000001
00000010
00000100
--------
00000111
sous la forme de
00000100
00000010
00000001
-------
00000111
ou sous n'importe quelle autre forme que pourrait autoriser la combinatoire de trois éléments, le résultat restera strictement le même et tu auras toujours un GO/NO GO sur 00000111 (j'ai un peu écrémé la taille de la clé par facilité )
Tu fais ton test sur la clé (00000111), et si la clé de ton entité est validée sur cette emprunte tu travaille sur les composants dans l'ordre qui t'intéresse
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Je sais pas pouquoi je n'arrivais pas à me faire une représentation mentale du cheminement effectué entre les 2 fonctions. Mais après coup, j'imagine que ça ressemble à ça :
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
...
...
...
...
...