JeuWeb - Crée ton jeu par navigateur
[Résolu] Comment stockez-vous vos quantités de ressources ? - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : [Résolu] Comment stockez-vous vos quantités de ressources ? (/showthread.php?tid=7103)

Pages : 1 2


[Résolu] Comment stockez-vous vos quantités de ressources ? - Sephi-Chan - 11-08-2013

Bonjour,

J'avance bien sur Seelies ces derniers temps et une question s'est récemment posée : la façon dont stocker une quantité de ressources.

J'ai plusieurs types de ressources (environ une dizaine), disons pour l'exemple : l'herbe, les feuilles, le bois et la pierre.

Et j'ai de nombreux usages de ces ressources :
  • Pour chaque parcelle, un type de ressource produit, sa quantité maximale, sa quantité actuelle et son facteur de régénération (exemple : la parcelle P produit une ressource R, il y a 100/120 unités dans la parcelle, avec une régénération de 2 unités par heure) ;
  • Pour chaque territoire, la quantité stockée pour chaque type de ressource (exemple : il y a 200 unités de bois et 40 unités de pierre sur le territoire T) ;
  • Pour chaque type de bête, sa capacité de récolte d'une bête pour chaque type de ressource (exemple : la fourmi peut ramasser 20 unités d'herbe par heure et 12 unités de feuille, le scarabée 6 unités de pierre par heure) ;
  • Pour chaque espèce de bête, sa consommation pour chaque type de ressource (exemple : la fourmi consomme 4 unités d'herbe ou 2 unités de feuille (qui en plus la soignent) par heure) ;
  • Pour chaque convoi, la quantité portée portée pour chaque type de ressource ;
  • Pour chaque territoire, la quantité réclamée comme "taxe" pour chaque type de ressource (et ce pour chaque "règle de contrôle à la frontière", on peut en avoir pour les alliés ou pour les ennemis) ;
  • Pour chaque territoire, la quantité de ressource qu'on est prêt à donner pour chaque type de bête qui spawn sur une parcelle voisine afin de la capturer (un système d'enchères avec les autres territoires voisin) ;

Mine de rien, ça fait pas mal d'usage (et j'en ai potentiellement d'autres qui ne sont pas encore définitifs) et il y a plus d'une façon de stocker ça.


Un système très souple mais un peu complexe

L'idée serait d'utiliser une table de relation (resource_type_thing), qui associe une quantité d'un type de ressource à n'importe quoi (un convoi, un type de bête, c'est polymorphe).

Pour ça, 5 colonnes :
  • resource_type_id : le type de ressource dont on représente une quantité ;
  • quantity : la quantité a proprement parler ;
  • thing_type : le type de chose à laquelle on attache cette quantité (convoi, territoire, type de bête) ;
  • thing_id : l'id de la chose à laquelle on attache cette quantité (convoi 42, territoire 66) ;
  • usage : quand l'élément auquel on attache la quantité de ressource a plusieurs usage d'un même type de ressource, on le différencie grâce à ça. Par exemple, un type de bête a une consommation et une capacité de récolte.


Au niveau de mes tables :
  • fields (qui fait le lien entre une zone de la carte et une partie) : resource_type_id, quantity, maximum_quantity et regeneration.
  • ownerships (qui fait le lien entre un territoire de la carte, une partie et une équipe) : plusieurs resource_type_things.
  • beast_types : pour la capacité de récolte, plusieurs resource_type_things (avec un usage harvesting) ; pour la consommation, plusieurs resource_type_things (avec un usage consumption).
  • convoys (convoi) : plusieurs resource_type_things.
  • territory_tax_rules (qui fait le lien entre un ownership, une équipe et une condition de collecte de taxe de passage) : plusieurs resource_type_things.
  • beast_type_capture_rules (qui fait le lien entre un ownership, une équipe et un type de bête) : plusieurs resource_type_things.


Un système simple mais rigide

L'autre approche est de stocker dans chaque table une colonne pour chaque ressource et chaque usage. Si j'ajoute une ressource dans le jeu, c'est un peu chiant.

Ainsi, dans ma table beast_types (qui a plusieurs usages), j'aurais des colonnes consumption_resource_type_1_quantity, consumption_resource_type_2_quantity, harvesting_resource_type_1_quantity.


Un système simple et flexible

Une autre approche consiste à stocker dans une colonne dédié du contenu sérialisé (via le type JSON ou Hash de PostgreSQL). Ainsi, dans la colonne resources de ma table beast_types (toujours avec ses usages mulitples), j'aurais quelque chose comme :


{
consumption: {
"resource_type_1" : 40,
"resource_type_2" : 90
},
harvesting: {
"resource_type_1" : 30
}
}


Voilà, qu'en pensez-vous ?
  • La solution 1 me semble bien complexe pour le besoin. Elle pourrait également être assez coûteuse en ressources.
  • La solution 2 est vraiment basique mais très fonctionnelle et efficace. Elle n'a que le côté pénible de réclamer un changement dans plusieurs table du schéma en cas d'ajout de ressources.
  • Enfin, la solution 3 est flexible et efficace et je ne sais pas trop ce qui lui manque.

Dans les 3 cas mon API côté application sera la même. Donc je n'ai qu'à prendre la meilleure pour mon cas.


RE: Comment stockez-vous vos quantités de ressources ? - Xenos - 11-08-2013

Salut,

sérialiser les données dans une BDD m'a toujours un peu fait pleurer... C'est un peu comme certains concepteurs que j'ai connu, qui étaient ravis d'avoir une base de données avec 2 tables: 1 qui contient absolument n'importe quel objet (sérialisé) et une qui contient les liens entre ces objets. Bon, ben à ce compte là, autant ne pas utiliser de SGDB et faire deux fichiers binaires sur son FTP.

La problématique production / consommation / stockage peut se développer comme un système d'équations mathématiques. Si tu sérialises, tu auras énormément de difficulté à envoyer ces équations au SGDB, puisqu'il faudra d'abord récupérer les données, puis les désérialiser, puis les traiter (souvent, on utilise alors non pas une solution exacte mais une approximation type Euler avec un intervalle de temps dT par exemple), puis les resérialiser, et enfin les remettre dans la BDD... C'est d'un lourd et d'un long !

J'hésiterai plutôt entre la solution souple, mais couteuse ou la solution rigide mais légère et simple. Ayant des mutualisés limités en terme de place pour la BDD, j'utilise plutôt la solution rigide légère. L'ajout d'une ressource change énormément le gameplay, et donc, on n'ajoute pas des ressources "à la volée", toutes les 30 secondes. Le fait que ce soit un peu chiant n'est pas vraiment un problème (si c'était impossible, ce serait différent).

Si t'as une place illimité, je dirai que le choix entre souple ou rigide dépendra de la fréquence d'ajout de ressources que tu comptes avoir. Si tu comptes laisser l'ajout de ressources possibles "juste au cas où", j'opterai pour la rigide simple. Si l'ajout de ressources est prévu et régulier, j'utiliserai la souple lourde. Dans aucun des deux cas je ne me tournerai vers les sérialisés. J'ajouterai d'ailleurs que niveau place, les données sérialisés sont très couteuses (il me semble), puisque ce sont des chaînes de caractères.

Ah, aussi, le système simple dépend de la densité de chaque ligne: si tu as 20 ressources différentes (donc, 20 colonnes si j'ai bien suivit), et que chaque unité n'a généralement qu'une ressource ou deux, alors 18 colonnes seront à "0", ce qui peut faire beaucoup de données inutiles. En ce cas, la solution souple mais "lourde" sera finalement plus légère.


RE: Comment stockez-vous vos quantités de ressources ? - Sephi-Chan - 11-08-2013

Bien sûr, l'usage abusif de sérialisation est à proscrire. Celui que je décris — qui utilise un mécanisme de la base de données (le type hstore de PostgreSQL, transparent à l'utilisation) est plutôt sain.

L'application sera hébergée sur du dédié, je peux me lâcher sur le stockage. Smile

Par ailleurs, je ne compte pas avoir des tonnes de ressources (la dizaine que j'évoque me semble un bon maximum), et je doute en rajouter souvent par la suite.

Je n'utiliserais les ressources que pour des résolutions ponctuelles, notamment à chaque changement d'heure. Puis par plus petites touches pour de l'affichage.


Je ne sais pas à quel point ce que tu décris concernant les équations mathématique me concerne (ce n'est vraiment pas un milieu dans lequel je suis à l'aise).


La densité des informations sera assez importante puisque la plupart des bêtes peuvent toucher un peu à tout. Donc je pense qu'il n'y aura peu de gâchis.

Après renseignement, les NULL ne coûtent presque rien dans PostgreSQL (gratuit pour une table jusqu'à 8 colonnes, puis 8 octets par tranche de 64 colonnes). Le stockage étant bon marché, il est plus intéressant de favoriser des colonnes NULL que des jointures.

D'ailleurs, pour ceux que ça intéresse, voici le lien vers le coût des NULL dans MySQL. Le fonctionnement des tables InnoDB (utilisé par défaut depuis MySQL 5.5) se rapproche de PostgreSQL (les NULL ne coûtent presque rien). C'est plus coûteux avec MyISAM.


Merci Xenos pour tes réponses : c'est analytique, c'est sympa. Smile


RE: Comment stockez-vous vos quantités de ressources ? - Xenos - 11-08-2013

Ok, hstore semble mieux que la bête chaine de caractères, mais je pense que la sérialisation (quelle que soit la dose) a un mauvais point: la structure (id des ressources) est stockée dans les données, et je pense qu'avec ce genre d'approche, il devient facile de s'emmêler les pinceaux.

Le NULL est couteux pour MyISAM mais ce dernier présente l'avantage (de mémoire) d'avoir des lignes de longueur fixe (tant qu'on n'utilise que des types entiers / flottants et non des chaines de caractères de longueure variable). C'est pratique en cas de crash, et cela speed un peu plus pour les requêtes.

Et donc, oui dans ce cas là avec NULL qui coute des cacahouètes, la structure rigide sera plus attrayante (tant pour les perfs que la maintenance) que la structure à jointures, surtout au niveau des index. Si jamais les tables font des kilomètres de long avec un nombre de colonnes déprimant, tu pourras toujours les scinder en deux.


RE: Comment stockez-vous vos quantités de ressources ? - Maks - 11-08-2013

Si tu cherches quelque chose de souple, pense au NoSQL. Si ça t'ennuies d'avoir deux bases de données, avec Postgres et les hstore ça avait l'air cool dans le Railscast. Perso, j'ai une base de donnée relationnelle assez conventionnelle pour les données qui s'y prêtent bien (gestions des membres, des news, du forum...) et Mongo pour les données de jeu qui nécessitent pas mal de ressources imbriquées. Le tout c'est d'avoir un bon ODM pour que ça soit pas le bordel (Mongoose peut gérer des jointures par exemple).


RE: Comment stockez-vous vos quantités de ressources ? - niahoo - 11-08-2013

Si tu veux jouer sur le faible coût des NULL ça serait pour la solution 2 si j'ai bien suivi, hein ?

Si j'ai bien suivi aussi (car j'avoue avoir un peu de mal ce "matin") tu veux stocker ces infos pour les types de bestioles nan ? Pas pour chaque bestiole appartenant à chaque joueur. Donc, tu peux pas tout simplement faire un gros JSON pour la persistance mais surtout le charger en RAM au démarrage du serveur ?

Ou même générer un fichier de code source ? car c'est le genre d'infos auxquelles on fait référence tout le temps et qui ne pèsent pas bien lourd en RAM.

Bon et si c'est bestiole par bestiole ben du coup vu que ton API est déjà fixée, tu peux switcher entre différentes solutions sans casse. Donc je prendrais la plus simple à implémenter, la 3 il me semble.


Un article intéressant qui décrit le modèle de données du CMS le plus rapide au monde, basé sur PostgreSQL avec, notamment, des genre de hash, mais pas que : http://zotonic.com/documentation/670/data-model-erd

(en fait les hash de données ne sont pas des hash postgresql, ils sont stockés dans un champ binaire (donc grosse sérialisation mais hyper rapide) et servent de référence, ensuite un cache avec de vraies colonnes est construit à partir de cette référence, et là on se rapproche plus de ta première solution mais c'est généré par du code, pas à la mano)

Et c'est d'ailleurs ce qui me semble le plus efficace à la longue : le dev se fait chier à faire un truc chiadé, mais ensuite le type qui pond les contenus fournit un simple JSON (ou un panneau d'admin facile à implémenter en pur JS) tandis que le code utilise des colonnes optimisées à mort.


RE: Comment stockez-vous vos quantités de ressources ? - Arnadus - 11-08-2013

Citation :Un système simple mais rigide

L'autre approche est de stocker dans chaque table une colonne pour chaque ressource et chaque usage. Si j'ajoute une ressource dans le jeu, c'est un peu chiant.

Ainsi, dans ma table beast_types (qui a plusieurs usages), j'aurais des colonnes consumption_resource_type_1_quantity, consumption_resource_type_2_quantity, harvesting_resource_type_1_quantity.

Sans vouloir m'avancer, il semble y avoir un problème d'abstraction ici.
Pourquoi ne pas considérer les ressources comme un type à part entière? (Et donc, pour parler au niveau BDD, comme une table)

Si tu crées chaque fois de nouveaux champs dans chaque entité qui requiert (= est lié) au concept de ressource, évidemment, c'est mauvais pour l'évolutivité et la maintenabilité.
Donc, pourquoi ne pas simplement extraire tes ressources dans une table dédiées? Et chaque entité qui aurait besoin de faire référence à ce concept de ressource serait lié (= foreign key) à une ligne dans cette table... Tout simplement.

Citation :usage : Par exemple, un type de bête a une consommation et une capacité de récolte.

Pour prendre un exemple plus concret, ton type "bête" aurait deux relations 1-1 vers la table "ressources". Ces deux relations seraient représentés par 2 foreign key.

la première: "consommation_ressource_id"
la deuxième: "capacite_recolte_id"


et il en va de même pour la notion de coût, de prime etc... Tout ce qui touche au ressource de près ou de loin devra être en lien avec cette table. De cette manière, si un jour tu veux ajouter un type de ressource, tu n'auras qu'à le faire dans la table "ressource".


RE: Comment stockez-vous vos quantités de ressources ? - niahoo - 11-08-2013

Comme modéliserais tu "la fourmi consomme 4 unités d'herbe ou 2 unités de feuille (qui en plus la soignent) par heure" avec ta solution ?


RE: Comment stockez-vous vos quantités de ressources ? - Arnadus - 11-08-2013

Il est très difficile de répondre à cette question en ayant cet énoncé pour seul support.
Mais si le concept de consommation doit également être raffiner, rien ne nous empêches de le faire.

On peut donc imaginer une entité "consommation" ayant 2 attributs:
  • ressources : Une entité de type Ressource
  • préférences : Difficile à dire mais à première vue, le mettrait également une entité de type Ressource. Ca à l'air de suffire pour l'exemple. Cette entité Ressource contiendrait en fait des informations de préférences sur les ressources consommées par l'unité. Là comme ça, à première vue, j'imagine bien un système de poids.
    Pour notre exemple, imaginons que la fourmis consomme de préférence de l'herbe et puis, si il n'y en a pas, se rabat sur les feuilles. On peut donc imaginer un poids de 2 pour l'herbe et un poids de 1 pour les feuilles. On peut également imaginer un poids négatif pour signifier qu'une ressource sera consommée de toutes façons, indépendamment des préférences.
    Concernant la quantité consommées, il faudrait aller voir dans l'attribut ressources. C'est là que se retrouveront les chiffres 4 pour l'herbe et 2 pour feuille.

Concernant le soin, là c'est encore plus vague et donc plus difficile pour moi de répondre.
Mais c'est toujours le même principe. Si le soin est attaché au concept de consommation, alors on attache un troisième attribut à l'entité consommation: "soin".
  • ressources : [inchangé]
  • préférences : [inchangé]
  • soin : là je vois également une entité de type Ressource. Si on imagine que manger 1 unité d'herbe rapporte 10 point de vie à la fourmis, 'suffit de mettre 10 dans l'attribut herbe de l'attribut soin.



RE: Comment stockez-vous vos quantités de ressources ? - Sephi-Chan - 11-08-2013

J'avais envisagé MongoDB avant de réaliser que mes données de jeu étaient vraiment très relationnelles. J'ai donc préféré rester sur une base taillée pour ça.

Pour la conservation en cache, je vais plutôt essayer de l'abstraire autrement car je ne suis pas fan des données hardcodées.


Arnadus, dans la première modélisation que je décris, la ressource est bien un type à part entière. Ce n'est plus le cas dans les deux autres modélisation, qui sont en échanges plus simples et efficaces (tant que ma problématique n'évolue pas trop, bien sûr).


Pour ce qui est de la nourriture, je vais partir sur des règles simples : la bête consomme ce qui la soigne en priorité si elle est blessée (sa priorité augmente avec la gravité de son état), en piochant dans ce qu'il y a en plus grande quantité d'abord. Certaines bêtes passent avant les autres (de la plus grosse à la plus petite).