Three.js: dépendances cycliques

Créé le 16 mars 2015  ·  81Commentaires  ·  Source: mrdoob/three.js

Salut tout le monde.

@kumavis et moi avons travaillé dur pour essayer de trouver un moyen efficace de déplacer THREE.js vers une architecture de navigateur. Nous avons fait de bons progrès, même au point d'avoir déplacé tous les fichiers vers un système de construction de navigateur et de pouvoir générer un three.min.js avec gulp.

Malheureusement, les exemples ne fonctionnent pas, car contrairement à commonjs, browserify ne peut pas gérer les dépendances cycliques, qui sont nombreuses dans THREE.js.

J'ai créé un graphique interactif illustrant les relations de dépendance ici .

À moins et jusqu'à ce que ceux-ci soient démêlés, nous ne pourrons pas déplacer THREE.js vers une version de navigateur.

Je ne considère pas cela comme une déficience de browserify, mais plutôt comme un problème avec THREE.js. Les dépendances circulaires sont une mauvaise chose dans les logiciels en général et entraînent toutes sortes de problèmes.

Suggestion

Commentaire le plus utile

@ Mugen87 ouah, 5 ans ! félicitations pour l'avoir enfin frappé :fire: :clap:
Je me suis beaucoup amusé à faire ces graphiques à l'époque :smile_cat:

Tous les 81 commentaires

c'est tout un noeud à démêler
http://jsbin.com/medezu/2/edit?html ,js,sortie
image

@coballast pouvez-vous poster le code que vous avez utilisé pour générer la dépendance json ?

Utilisez simplement le fichier précompilé three.min.js directement. Il n'est pas nécessaire de diviser Three.js en fichiers individuels dans Browserfy, vous ne faites que vous rendre la vie plus difficile sans réel avantage.

Je parle d'expérience, dans la mesure où nous utilisons le module npm de three.js et cela fonctionne très bien. Nous l'emballons simplement dans un fichier unique et l'enveloppons dans un module de style CommonJS. Cette approche fonctionnerait pour browserfy et beaucoup de gens le font déjà, je comprends.

Le démêlage de ce nœud n'est pas nécessaire pour ce cas d'utilisation.

@kumavis Je viens de vider la structure de dépendance. Le code suivant a ensuite généré le graphique :

var fs = require('fs-extra');
var unique = require('uniq');
var util = require('util');

function getRequiredObjects(dependencies){
  var result = [];
  for(var i = 0; i < dependencies.usedObjects.length; i++){
    var object = dependencies.usedObjects[i];
    if(dependencies.definedObjects.indexOf(object) == -1)
      result.push(object);
  }

  return result;
};

var dependencies = JSON.parse(fs.readFileSync('./dependencies.json'));

var objects = [];
for(var f in dependencies){
  objects = objects.concat(dependencies[f].usedObjects);
  objects = objects.concat(dependencies[f].definedObjects);
}

objects = unique(objects);


var nodes = objects.map(function(o){
  return {data: {id: o} };
});

var edges = [];
for(var f in dependencies){
  var dependency = dependencies[f];
  var requiredObjects = getRequiredObjects(dependency);
  for(var j = 0; j < dependency.definedObjects.length; j++){
    for(var k = 0; k < requiredObjects.length; k++){
      edges.push({ data: { source: dependency.definedObjects[j], target: requiredObjects[k] } });
    }
  }
}

var graph = {nodes: nodes, edges: edges};

var eliminateImpossibleCycleNodes = function(graph){
  graph.nodes = graph.nodes.filter(function(node){
    var source_edge = null;
    var dest_edge = null;
    for(var i = 0; i < graph.edges.length; i++){
      if(graph.edges[i].data.source == node.data.id)
        source_edge = graph.edges[i];
      if(graph.edges[i].data.target == node.data.id)
        dest_edge = graph.edges[i];
    }

    if(source_edge != null && dest_edge != null)
      return true;
    else
      return false;
  });

  graph.edges = graph.edges.filter(function(edge){
    var source_exists = false, target_exists = false;
    for(var i = 0; i < graph.nodes.length; i++){
      if(edge.data.source == graph.nodes[i].data.id)
        source_exists = true;
      if(edge.data.target == graph.nodes[i].data.id)
        target_exists = true;
    }

    return source_exists && target_exists;
  });
};

for(var i = 0; i < 500; i++)
  eliminateImpossibleCycleNodes(graph)


console.log(JSON.stringify(graph));

@bhouston en savoir plus sur la santé de la base de code three.js

Je sais juste que dans la bibliothèque de mathématiques, que j'ai beaucoup aidé, les dépendances cycliques sont la norme dans toutes les langues. Parce que les fonctions sur Matrix4 peuvent prendre Vector3 comme paramètres, et Vector3 peut être transformé par Matrix4. Rendre toutes les dépendances unidirectionnelles dans la bibliothèque mathématique rendrait cette partie de la bibliothèque ennuyeuse à utiliser.

Maintenant, je préconise que la bibliothèque mathématique ne connaisse aucune autre partie de la bibliothèque - les types plus complexes ne devraient pas vraiment s'infiltrer dans ce module. Ainsi, dans ce sens, je préconise d'essayer de réduire les dépendances cycliques inter-modules, mais de ne pas supprimer toutes les dépendances cycliques entre les fichiers individuels au sein d'un module.

Voici un cas qui illustre les complications subtiles. Pour être clair, ici je ne critique pas la mise en œuvre elle-même, mais les effets secondaires.

Vector3 et Matrix4 forment une dépendance cyclique car ils exposent une gamme de fonctions qui s'utilisent comme types d'entrée ou de sortie. Les deux sont implémentés avec un style commun à Three.js, définissant des fonctions via IIFE pour inclure des variables scratch pour effectuer des calculs.

Matrix4#lookAt est capable d'instancier le scratch immédiatement, dans le cadre de la définition de la fonction.

lookAt: function () {

  var x = new THREE.Vector3();
  var y = new THREE.Vector3();
  var z = new THREE.Vector3();

  return function ( eye, target, up ) {
    /* ... */

Vector3#project cependant, doit instancier le scratch lors de la première exécution.

project: function () {

  var matrix;

  return function ( camera ) {

    if ( matrix === undefined ) matrix = new THREE.Matrix4();

    /* ... */

Pourquoi? car lors de la définition de la classe, toutes les classes n'ont pas encore été définies. Lors de la définition Vector3 , Matrix4 n'existe pas encore. Maintenant, le temps d'instanciation réel des variables scratch n'est pas vraiment important. Le vrai point à retenir ici est que l'implémentation actuelle dépend de l'ordre dans lequel le système de construction concatène les fichiers. Il s'agit d'un couplage très éloigné, et les modifications apportées au système de construction ou le renommage des fichiers de telle sorte qu'il modifie l'ordre concat peuvent entraîner des constructions invalides, sans connexion évidente.

Ce n'est qu'une des façons dont ce nœud se manifeste en bugs. Cependant, bien que nous puissions résoudre ce problème spécifique, je n'ai pas de solution générale qui ne nécessite pas de nombreuses modifications de rupture de l'API.

Hmm ... J'ai jeté un coup d'œil à la bibliothèque mathématique C++ d'ILM, que je considère comme la référence en matière de bibliothèques mathématiques. Étonnamment, ils ne sont pas cycliques. Ils ont essentiellement un ordre bien défini de types simples à complexes qu'ils définissent et je suppose que si vous le faites très soigneusement, cela fonctionne:

https://github.com/openexr/openexr/tree/master/IlmBase/Imath

Math puis Vec semblent être les plus simples.

Autres observations sur le graphique de dépendance :

Material s ont des profondeurs bidirectionnelles avec leur classe de base
image
Un peu difficile à voir, mais Geometry semblent avoir de bonnes dépenses à sens unique sur la classe de base
image
Light s et Camera s ont une situation similaire -- bien par rapport à leur classe de base, mais la dépendance Object3D _sur eux_ semble inutile.
image
image
Curve s Path s Line s semblent bons, mais Shape est un peu emmêlé.
image

@coballast merci ! c'est une grande perspicacité.

Je m'ajoute pour les commentaires :)

Au fait, j'ai regardé dans la façon dont Material dépend de MeshDepthMaterial, par exemple. C'est simple

if ( this instanceof THREE.MeshDepthMaterial )

ce qui est trivial de changer en

if ( this.type == 'MeshDepthMaterial' )

et voilà - pas de dépendance. Je suppose que la moitié de ce graphique effrayant est le même niveau de problème.

Ce qui est drôle, c'est que cette dépendance a lieu dans une seule méthode toJSON. Je veux dire, ne pourrait-il pas être simplement remplacé dans MeshDepthMaterial à la place? quelque chose comme

THREE.MeshDepthMaterial.prototype.toJSON =  function () {
    var output = THREE.Material.prototype.toJSON.apply(this);
    if ( this.blending !== THREE.NormalBlending ) output.blending = this.blending;
    if ( this.side !== THREE.FrontSide ) output.side = this.side;

@makc En général, partout où nous faisons instanceof , nous devrions déplacer ce code vers la classe spécifique elle-même. Cela aidera à éliminer beaucoup de nœuds.

Je voulais juste dire que même si AMD ne prend pas en charge les références circulaires, les modules ES6 le font https://github.com/ModuleLoader/es6-module-loader/wiki/Circular-References-&-Bindings

je suis juste curieux de savoir à part le problème de résolution de dépendance (qui peut être résolu dans la mise en œuvre d'un chargeur de système de module, par exemple system.js ), quels problèmes le référencement circulaire dans three.js crée-t-il?

Heureusement, il semble que nous pouvons attaquer cela par étapes. Je pense que de nombreuses modifications de rupture d'api ont été apportées dans les versions précédentes, donc je ne suis pas sûr que ce soit hors de question.

Pour les cas instanceof (peut-être la majorité), ils devraient pouvoir être résolus sans modifications majeures.

Je m'abonne aussi ici. Allons étape par étape ici.
Je suis d'accord que nous devrions supprimer toutes les dépendances cycliques inutiles comme celle matérielle.
Je suis également d'accord avec @bhouston sur le fait que la bibliothèque mathématique est très dépendante l'une de l'autre car l'interaction est ce qui rend la bibliothèque mathématique utile.

Quelqu'un peut-il tracer les plus faciles ? ? Avoir moins de dépendances cycliques est toujours une bonne idée si cela ne gêne pas la bibliothèque. Nous pourrons voir plus tard ce qu'il faut faire des autres.

@ zz85 J'ai également rencontré le problème des dépendances circulaires. C'est surtout un problème lorsque nous essayons de pré-créer certains objets dans des fichiers référencés circulaires.

6252 devrait éliminer beaucoup de profondeurs circulaires sur Material et Object3D .

Voici à quoi ressemble Mesh . Peut-être quelques profondeurs étrangères mais pas trop folles.
image

Circulaire avec Object3D et Geometry . La référence Object3D -> Mesh est traitée dans le PR ci-dessus. La référence Mesh -> Geometry est correcte, b/c Mesh contrôle une instance de Geometry . Il pourrait toujours être interrompu car il effectue une vérification de type pour un comportement spécifique à la classe ( Geometry / BufferGeometry ).

Quant à la référence Geometry -> Mesh , elle est à fournir geometry.mergeMesh( mesh ) . Geometry est un concept de niveau inférieur à Mesh , donc je l'inverserais en mesh.mergeIntoGeometry( geo ) et déconseillerais mergeMesh .

Si quelqu'un obtient un pr fusionné qui corrige certains d'entre eux, faites-le moi savoir et je mettrai à jour le graphique pour refléter l'état actuel des choses.

@bhouston @gero3 Je ne suis pas convaincu que des dépendances cycliques soient nécessaires pour obtenir le même niveau de convivialité/utilité pour la bibliothèque mathématique. Je peux me tromper, mais ne pourrions-nous pas garder Vector3 complètement isolé/ignorant du reste du système et modifier son prototype pour accueillir Matrix4 dans le module Matrix4 ? Cela a du sens pour moi conceptuellement, puisque les matrices sont plus complexes que les vecteurs. Je pense qu'il est préférable d'avoir un ordre bien défini dans lequel les prototypes et les classes sont construits pour éviter les accidents.

@bhouston @gero3 Je pense que nous pourrons peut-être le faire sans changer du tout l'API. Je vais fouiller et voir ce qu'il en est.

en ce qui concerne les mathématiques, vous pourriez simplement avoir tous les "blocs-notes" assis au même endroit, je suppose. mais je parie qu'il n'y aura pas un seul graphique utilisable de 3js qui n'aurait pas à la fois Vector3 et Matrix4

S'il existe une solution qui ne modifie pas les performances ou l'API de la bibliothèque mathématique, je suis tout à fait d'accord.

@coballast ne peut pas supprimer le dep cyclique sans changement d'API, car les deux fournissent des méthodes qui utilisent l'autre type. Vector3 Matrix4

En ce qui concerne la compatibilité de navigateur, notre seule exigence est de déplacer l'instanciation sur les vars scratch hors du temps de définition de classe (faites-les instancier lors de la première exécution). Faites ce paresseux comme ça . Cela n'aurait aucun impact sur l'API ou les performances.

Je pense que ces types de changements sont acceptables.

@kumavis Ah ! Oui. D'accord, je comprends maintenant. C'est assez facile.

Je soutiens pleinement que TROIS soit divisé en modules plus petits avec une structure requise, pour les raisons suivantes :

  1. TROIS est très grand. Si un utilisateur ne pouvait exiger que ce dont il avait besoin, cela pourrait réduire la taille de la construction du client. Par exemple , react-bootstrap vous permet de faire des choses comme var Alert = require('react-bootstrap/lib/Alert'); qui ne regroupe pas tous les modules bootstrap.
  2. Il existe des "plugins", comme OrbitControls.js qui modifient l'objet global THREE lui-même, se mettant sur THREE.OrbitControls . Il s'agit d'un anti-modèle dans les frameworks javascript modernes car il nécessite que TROIS soit présent sur l'espace de noms global dans un processus de construction, au lieu d'être requis par les fichiers qui en ont besoin. THREE le fait également en interne, en modifiant toujours l'espace de noms THREE, ce qui n'est pas idéal pour inclure des modules THREE spécifiques.

se mettant sur TROIS.OrbitControls

mais chaque morceau de code en 3js fait ça?

@DelvarWorld a écrit :

Je soutiens pleinement que TROIS soit divisé en modules plus petits avec une structure requise, pour les raisons suivantes :

J'avais l'habitude de penser que c'était une bonne idée de le diviser, mais il y a une simplicité à ThreeJS tel qu'il est en ce moment. Il est plus utilisable par ceux qui découvrent la 3D sous sa forme actuelle et cela a été une priorité pour l'équipe de développement. Vous pouvez utiliser ThreeJS sans avoir besoin d'un système de modules (dont il y en a beaucoup, et ils ne sont pas tous entièrement compatibles.)

Comme @makc , je suis également perplexe quant à la suggestion de @DelvarWorld de ne pas mettre les choses dans l'espace de noms THREE.

Où/comment seraient-ils à la place ?

Pour moi, il semble que le bon modèle consiste à créer un seul objet global, TROIS, dans lequel toutes les parties (et éventuellement certaines extensions/plugins) de celui-ci se trouvent.

Je suis d'accord avec @DelvarWorld que la technique de mise sur le global n'est pas bonne pour la santé de la base de code - c'est une sorte d'argument subtil car le mettre sur le global lui-même n'est pas le problème, c'est le graphe de dépendance caché, et d'autres pratiques qui découlent de la disponibilité du global.

Mais cet argument est principalement limité au développement interne et à la structure du code. En ce qui concerne la livraison de la bibliothèque sous la forme d'un ensemble de codes statiques, placer toutes les classes sur le TROIS global me semble logique.

Un contre-argument est que lors de la désérialisation d'une scène THREE.js json, les entrées peuvent simplement répertorier leur classe sous la forme d'une chaîne pouvant être extraite du global comme: THREE[ obj.type ] . Cela fonctionne avec les classes qui ne figurent pas dans la bibliothèque standard three.js tant que vous les définissez sur THREE avant de désérialiser. Vous ne savez pas comment remplacer au mieux ce comportement sans le THREE global.

Cela fonctionne avec les classes qui ne figurent pas dans la bibliothèque standard three.js tant que vous les définissez sur TROIS avant de désérialiser. Vous ne savez pas comment remplacer au mieux ce comportement sans le THREE global.

Vous pouvez faire ce modèle (ou une variante de celui-ci) si tout est un module :

var objectType = require( "THREE." + obj.type );

Il y a beaucoup de changements à venir avec ES6 en ce qui concerne les modules. Je revisiterais la modularité de ThreeJS à ce moment-là.

La version construite de trois (le fichier javascript que les gens peuvent télécharger manuellement) aurait toujours tout sur l'espace de noms trois. vous pouvez le faire avec le fichier de point d'entrée pour la construction :

var THREE = {
    Geometry: require("./geometry"),

etc, ce qui serait toujours utile aux nouveaux arrivants et facile à démarrer.

Pour ceux qui utilisent trois de npm et requirejs/browserify/webpack dans une version javascript moderne, nous pourrions faire quelque chose comme

var Scene = require("three/scene"),
     Camera = require("three/camera"),

etc, ce qui pourrait réduire une partie de la taille de trois intégrée dans un ensemble de taille client. Je peux me tromper car je ne sais pas combien de trois est "core". Pour le moment, cependant, cela est impossible car trois n'utilisent pas d'instructions require .

dans tous les cas, le modèle moderne nécessite des modules, et au lieu de faire en sorte que tout votre code modifie une autre bibliothèque (en modifiant le global TROIS, mauvais), votre code est indépendant et modulaire, et spécifie ce dont il a besoin avec des instructions require , comme le code source de React .

Je ne pense pas qu'essayer de faire un argument complet pour utiliser la syntaxe require/module sera utile, car il existe de nombreuses bonnes ressources en ligne à ce sujet. Mais il est mauvais d'encourager les autres à modifier l'espace de noms THREE pour ajouter des plugins comme OrbitControls.

Sachez simplement @DelvarWorld que ES6 introduit officiellement des modules dans JavaScript avec une syntaxe très spécifique et distincte :

http://www.2ality.com/2014/09/es6-modules-final.html

@bhouston oh ouais totalement, je suis agnostique pour exiger vs importer (l'importation est probablement le meilleur choix), prenant simplement en charge le modèle de module en général.

@bhouston @DelvarWorld @kumavis Un de mes projets à long terme est d'écrire un convertisseur automatique es5 -> es6 qui peut accueillir et convertir des modules commonjs/amd en es6, et j'espère identifier et réécrire beaucoup de javascript en utilisant des constructions es6 comme des classes/générateurs etc. On pourrait utiliser une transformation browserify comme es6ify pendant que les navigateurs rattrapent la norme pour préparer le code à la consommation. Faire passer TROIS à browserify uniquement au niveau interne est une bonne première étape pour le préparer à l'entrée dans un tel outil.

Ce n'est pas le point que j'essayais (apparemment très mal) de faire cependant. Je souhaite supprimer autant de ces dépendances cycliques que possible, indépendamment de tout problème de modularité, car je pense que cela rendra TROIS plus stable, plus flexible et éliminera probablement beaucoup de bogues comme un effet secondaire agréable.

@coballast https://github.com/mrdoob/three.js/pull/6252 a été fusionné, devrait réduire une grande partie des dépenses cycliques. Vous pensez pouvoir générer un nouveau graphique de profondeur ? Peut-être en faire un utilitaire dans le dépôt de l'outil de conversion

ensuite : faire en sorte que les variables scratch dans Vector3 Matrix4 soient définies paresseusement lors de la première utilisation, pas au moment de la définition

quelqu'un veut faire du bénévolat? devrait être rapide

Le graphique a été mis à jour. http://jsbin.com/medezu/3/

Voici une capture d'écran :

snapshot3

Je suis heureux d'annoncer qu'un grand nombre de circ deps d'Object3Ds ont été éliminés. Bon travail @kumavis !

wow est-ce la même base de code? fou

Travaillera à faire de la génération de graphes une partie de l'utilitaire.

À la seule inspection du graphique, Shape et Geometry semblent être des arbres de classes possibles qui peuvent être démêlés.

@coballast pense que tu peux t'en charger ?

rendre les vars scratch dans Vector3 Matrix4 définis paresseusement lors de la première utilisation, pas au moment de la définition

ce serait un PR contre dev en amont par opposition à un changement automatisé

Je pense également que nous pouvons rétrograder le titre du problème de " Problèmes graves de dépendance cyclique" à " Dépendances cycliques" – la situation s'est beaucoup améliorée !

@kumavis Bien sûr. Y travaillera quand le temps le permettra.

Voici l'état actuel du nettoyage de l'interdépendance tel que je le vois :
(les flèches indiquent les connexions qui doivent être supprimées le cas échéant)

  • [x] Matériaux
  • [x] Géométries
  • [x] Object3D
  • [x] Mathématiques
  • [x] Formes

    • [x] Forme -> FontUtils

    • [x] Forme -> ExtruderGéométrie

    • [x] Forme -> FormeGéométrie

    • [x] Chemin -> Forme

  • [ ] Encadré3

    • [ ] Box3 -> BufferGeometry

    • [ ] Case3 -> Géométrie

Formes:

image

Case3 :

image

Math:

Ces nœuds sont interconnectés, mais offrent une commodité suffisante grâce à cela.
image

Shape semble avoir des dépendances avec ExtrudeGeometry et ShapeGeometry via un code comme celui-ci :

// Convenience method to return ExtrudeGeometry

THREE.Shape.prototype.extrude = function ( options ) {

  var extruded = new THREE.ExtrudeGeometry( this, options );
  return extruded;

};

// Convenience method to return ShapeGeometry

THREE.Shape.prototype.makeGeometry = function ( options ) {

  var geometry = new THREE.ShapeGeometry( this, options );
  return geometry;

};

Il apparaît maintenant que Shape est une sous-classe de Path , et que ExtrudeGeometry et ShapeGeometry sont toutes deux des sous-classes de Geometry . Donc, vous savez, vous pouvez déterminer quelles dépendances devraient disparaître dans le cas idéal.

Oui, cela relève de la même catégorie que Vector3 <-> Matrix4 . Ils sont liés pour plus de commodité. Je pense que c'est une mauvaise idée mais ça ne vaut pas la peine de la combattre. Je vais le marquer comme complet

Shape -> FontUtils pourrait être supprimé en utilisant des méthodes de déplacement comme triangulate vers des utilitaires plus génériques. Mais pas une grosse victoire pour faire ça. Je vais le marquer comme terminé.

Box3 -> BufferGeometry et Box3 -> Geometry pourraient tous deux être nettoyés.

C'est un autre cas de ne pas mettre un comportement dépendant de la classe sur la classe elle-même.

origine :

setFromObject: function () {

  // Computes the world-axis-aligned bounding box of an object (including its children),
  // accounting for both the object's, and childrens', world transforms

  /* ... */

  if ( geometry instanceof THREE.Geometry ) {
    /* ... */
  } else if ( geometry instanceof THREE.BufferGeometry && geometry.attributes[ 'position' ] !== undefined ) {
    /* ... */
  }

  /* ... */

}

Dans les deux cas, il essaie simplement de parcourir les sommets/positions worldCoordinate de la géométrie. Je me demande si cela simplifierait grandement le code pour créer un objet paresseux vertices sur BufferGeometry qui recherche les valeurs telles qu'elles sont demandées. Pas sûr de l'impact sur les performances.

Alternativement, nous pourrions utiliser Geometry.computeBoundingBox :
Geometry
BufferGeometry

Box3 est un problème lors de l'exécution de la construction de browserify. Voir les notes dans coballast/threejs-browserify-conversion-utility#21.

@kumavis Cela vous dérangerait-il de décrire votre solution recommandée pour traiter Box3/Geometry/BufferGeometry ? Si c'est rapide, je peux l'implémenter.

Je ne peux pas le regarder pour le moment, mais je commencerais par ma suggestion ci-dessus d'utiliser geo.computeBoundingBox tel qu'implémenté sur Geometry et BufferGeometry à la place du if/else ici . Au lieu de cela, box3.setFromObj devrait appeler geometry.computeBoundingBox , puis définir les paramètres en fonction de la boîte produite3.

Cela devrait supprimer les extrémités Box3 -> BufferGeometry et Box3 -> Geometry des profondeurs circulaires. Faites-moi savoir s'il me manque quelque chose.

Hmm peut-être que le code résultant est un peu compliqué, qu'est-ce qui a du sens ici ? Box3.setFromObject ne devrait pas exister, mais ce n'est pas une option. Geo devrait être capable de produire des box3, je n'ai aucun problème avec ça. Ouais, je suppose que Box3.setFromObject devrait demander au géo pour une boîte englobante / étendues, mais peut-être qu'ils devraient demander au Object3D / Mesh pour la boîte englobante / étendues.

désolé j'ai été un peu décousu. laissez-moi savoir ce que vous pensez.

Peut-être pertinent : #6546

Sans quelque chose comme ça, il est impossible d'analyser ces dépendances dynamiques à partir de scripts de chargeur.

D'après mes tests, les dépendances cycliques ne sont pas un problème avec commonJSification. Ils doivent être manipulés correctement, et comme indiqué précédemment dans ce fil, ils rendent le graphique de dépendance assez gênant, mais ils n'empêchent pas THREE.js de fonctionner sur des environnements commonJS (lorsqu'ils sont transformés, bien sûr).

Je viens de publier une version complète de commonjs sur npm en tant que three.cjs en utilisant mes trois transpiler commonjs

Remarque : pour que cela fonctionne, j'ai dû sélectionner manuellement #6546 sur le maître. Bien que les dépendances dynamiques fonctionnent bien dans node.js, elles ne peuvent pas fonctionner dans Browserify (ou tout autre outil cjs vers navigateur) car elles doivent effectuer une analyse de dépendance statique.

Preuve de navigateur : http://requirebin.com/?gist=b7fe528d8059a7403960

@kamicane FYI - Voici où TROIS a été ajouté comme argument d'une fonction anonyme à Raycaster (anciennement Ray ).

Je comprends la nécessité d'une fonction anonyme (pour éviter les fuites globales dans les navigateurs), l'argument est cependant superflu et fait tomber l'ensemble du fichier dans la catégorie des dépendances calculées. Bien que l'argument puisse être supprimé dynamiquement avec des modifications AST, il ne peut jamais être une solution à l'épreuve des balles (selon ce qui est écrit dans l'argument, lu à partir de l'argument, etc.). L'analyse statique devient presque impossible. Une intervention manuelle est nécessaire dans ce cas.

Maintenant que Raycaster est plus léger, nous pourrions essayer de le faire comme le reste des classes.

@mrdoob , @Mugen87 nous utilisons Rollup pour n'utiliser que les parties de Three.js dont nous avons vraiment besoin. Lorsque nous exécutons le build, nous recevons toujours l'avertissement suivant :

(!) Circular dependency: node_modules/three/src/math/Vector3.js -> node_modules/three/src/math/Matrix4.js -> node_modules/three/src/math/Vector3.js
(!) Circular dependency: node_modules/three/src/math/Vector3.js -> node_modules/three/src/math/Quaternion.js -> node_modules/three/src/math/Vector3.js
(!) Circular dependency: node_modules/three/src/math/Sphere.js -> node_modules/three/src/math/Box3.js -> node_modules/three/src/math/Sphere.js
(!) Circular dependency: node_modules/three/src/objects/LineSegments.js -> node_modules/three/src/objects/Line.js -> node_modules/three/src/objects/LineSegments.js

Y a-t-il encore des dépendances circulaires dans Three.js ou faisons-nous quelque chose de mal ?

Vector3 et Matrix4 sont liés l'un à l'autre, si vous en tirez un, vous devez tirer l'autre. Les dépendances circulaires devraient techniquement être autorisées.

@bhouston ouais je vois, merci pour l'indice. Oui, les dépendances circulaires sont autorisées et le cumul ne pose aucun problème, mais je ne sais pas si c'est une bonne pratique d'avoir des dépendances circulaires. Vector3 ne dépend que de Matrix4 cause de multiplyMatrices et getInverse , pour plus de détails voir (https://github.com/mrdoob/three.js/ blob/dev/src/math/Vector3.js#L315)

@roomle-build Idk, mec, juste parce qu'il fait explicitement référence au constructeur Matrix4 ? qu'en est-il de

    applyMatrix4: function ( m ) {

        var x = this.x, y = this.y, z = this.z;
        var e = m.elements;

        var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );

        this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;
        this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;
        this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;

        return this;

},

?

vous pouvez dire que vous pouvez passer { elements: [....] } et cela fonctionnera, mais nous savons tous qu'il attend Matrix4 ici

Commençons par Vector3 .

Vector3 dépend de Matrix4 cause de project et unproject :

    project: function () {

        var matrix = new Matrix4();

        return function project( camera ) {

            matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) );
            return this.applyMatrix4( matrix );

        };

    }(),

    unproject: function () {

        var matrix = new Matrix4();

        return function unproject( camera ) {

            matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) );
            return this.applyMatrix4( matrix );

        };

    }(),

Vector3 dépend de Quaternion cause de applyEuler et applyAxisAngle :

    applyEuler: function () {

        var quaternion = new Quaternion();

        return function applyEuler( euler ) {

            if ( ! ( euler && euler.isEuler ) ) {

                console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );

            }

            return this.applyQuaternion( quaternion.setFromEuler( euler ) );

        };

    }(),

    applyAxisAngle: function () {

        var quaternion = new Quaternion();

        return function applyAxisAngle( axis, angle ) {

            return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );

        };

    }(),

Suggestions?

Je ne sais pas si nous devons absolument supprimer les dépendances circulaires. Mais je pourrais imaginer déplacer multiplyMatrices vers un module Math . La signature changerait alors bien sûr en multiplyMatrices( a: Matrix4, b: Matrix4, result: Matrix4 ): Matrix4 . À l'intérieur Vector3 , vous pourriez alors import { multiplyMatrices } from './Math'; faire la même chose dans Matrix4 (pour conserver la même surface API de Matrix4 ).

Je viens de jeter un coup d'œil rapide (je n'ai pas regardé le cas Quaternian - seulement Vec3/Mat4 ) et je ne suis pas sûr non plus de l'implication des performances et des conséquences pour le reste de la base de code. De plus, je ne suis pas non plus convaincu qu'il soit absolument nécessaire de supprimer ces dépendances circulaires. Je voulais juste partager ma pensée parce que @mrdoob a demandé des suggestions

@roomle-build crée donc essentiellement plus de modules pour éviter les dépendances circulaires, mais vous allez toujours utiliser tous ces modules ? cela aurait peut-être plus de sens si chaque méthode mathématique était son propre module, alors vous ne tirez que ceux que vous utilisez, mais ce sera beaucoup de modules.

@makc pas vraiment. Ce serait un gros module avec beaucoup de petites fonctions "d'assistance". Cela aiderait également à secouer les arbres, etc. Un module mathématique pourrait ressembler à :

export const multiplyMatrices( a, b, result ) { // ... DO STUFF ... // }
export const getInverse( /* ... */ ) { // ... DO STUFF ... // }
// ...
// ...

Et le module de consommation ferait quelque chose comme :

import { Matrix4 } from './Matrix4.js';
import { multiplyMatrices } from './math';
const result = new Matrix4( );
multiplyMatrices( a, b, result );

Lorsque vous regroupez tout, le rollup fait sa magie et crée le bundle le plus efficace.

C'est ce que font beaucoup de bibliothèques populaires. En fait, RxJS a également basculé sa "logique" import sur le modèle que j'ai décrit. Là, ça ressemble à quelque chose comme :

 import { flatMap, map, tap } from 'rxjs/operators';

myObject.run().pipe(
  tap(result => doSomething()), 
  flatMap(() => doSomethingElse()), 
  map(() => doAnotherThing())
);

Vous pouvez lire sur le "pourquoi et comment" ils ont changé ce truc dans RxJS 6 dans plusieurs articles de blog par exemple : https://auth0.com/blog/whats-new-in-rxjs-6/

Mais comme je l'ai dit, ce n'est qu'une idée et je ne suis pas sûr de toutes les implications que cela aurait sur le reste de la base de code. De plus, le module math actuel n'est pas "préparé" pour être utilisé comme ça. Actuellement, toutes les méthodes du module mathématique sont attachées "en quelque sorte statiques". Cela empêche également le cumul de détecter ce qui est vraiment nécessaire...

@roomle-build hmm donc vous dites que le cumul peut comprendre si le code dans la même portée n'a pas réellement besoin de toute la portée, bien.

Vous parlez d'évoluer vers une approche fonctionnelle (fonctions prenant des objets) plutôt qu'une approche orientée objet (objet ayant des fonctions membres.) C'est une chose réelle mais étant donné que Three.JS est entièrement orienté objet, proposer ce type de changement est un assez gros et cela casserait tout le code existant.

Je ne suis pas sûr que les arguments en faveur de ce changement soient suffisamment importants à ce stade pour justifier la rupture de toute rétrocompatibilité.

@makc pas vraiment. Ce serait un gros module avec beaucoup de petites fonctions "d'assistance". Cela aiderait également à secouer les arbres, etc. Un module mathématique pourrait ressembler à :

Si c'est ce qui est proposé, il faut le décrire correctement. C'est le changement de Three.JS d'un style de conception orienté objet à un design fonctionnel.

@roomle-build hmm donc vous dites que le cumul peut comprendre si le code dans la même portée n'a pas réellement besoin de toute la portée, bien.

oui rollup comprend comment toutes les importations sont liées les unes aux autres et fait du tree-shaking, de l'élimination du code mort, etc. Les nouvelles versions de rollup peuvent aussi faire du "chunking" et beaucoup d'autres trucs sympas. Mais la structure actuelle du projet ne tire pas pleinement parti de ces fonctionnalités.

Vous parlez d'évoluer vers une approche fonctionnelle (fonctions prenant des objets) plutôt qu'une approche orientée objet (objet ayant des fonctions membres.) C'est une chose réelle mais étant donné que Three.JS est entièrement orienté objet, proposer ce type de changement est un assez gros et cela casserait tout le code existant.

Je ne pense pas que ces deux paradigmes soient mutuellement exclusifs. Je pense que vous pouvez mélanger et assortir ces deux paradigmes. Je ne propose pas non plus de passer à la programmation fonctionnelle. Je voulais juste décrire un moyen de se débarrasser de la dépendance cyclique. Vous pouvez également attacher la méthode multiplyMatrices à l'objet Math . Mais si quelqu'un réécrit ce genre de choses, il serait logique d'envisager d'utiliser les fonctionnalités des modules ES6. Mais comme je l'ai dit, je ne suis pas un expert de la base de code Three.js et c'était juste une idée pour éliminer la dépendance cyclique. Je pense que Three.js est un projet génial avec une excellente base de code et je ne veux pas le harceler. Alors j'espère que personne ne se sent offensé par mes commentaires 😉

Je ne sais pas si nous devrions discuter des décisions de conception dans un numéro. Avez-vous un endroit où ce genre de choses convient mieux?

BTW gl-matrix est une bibliothèque mathématique fonctionnelle : https://github.com/toji/gl-matrix/tree/master/src/gl-matrix

@roomle-build

Actuellement, toutes les méthodes du module mathématique sont attachées "en quelque sorte statiques".

Comment?

@mrdoob Je crois qu'avec la conception fonctionnelle de gl-matrix, chaque fonction de vec3 (dans le fichier vec3 auquel j'ai lié dans mon commentaire précédent) est exportée individuellement. Cela vous permet de choisir les fonctions à importer. Vous n'avez pas besoin d'apporter tout le vec3.

Comme avec Three.JS, parce qu'il utilise une conception orientée objet, toutes les fonctions mathématiques de Vector3 sont attachées au prototype d'objet Vector3 et vous n'importez que la classe Vector3 elle-même.

Ainsi, les importations dans Three.JS sont de classes entières alors qu'avec une approche fonctionnelle, vous importez des fonctions individuelles.

(L'autre chose très intéressante à propos de la bibliothèque gl-matrix est que toutes les fonctions individuelles n'utilisent pas d'autres fonctions, @toji a essentiellement intégré une version optimisée de toutes les mathématiques dans chaque opération individuelle. C'est probablement assez efficace en termes de vitesse mais cela conduit à une bibliothèque difficile à maintenir.)

Je ne pense pas que nous ayons besoin de refactoriser cette partie de Three.JS, sauf peut-être pour nous débarrasser de toute référence dans /math à d'autres répertoires dans three.js. La bibliothèque mathématique est assez petite et elle n'apparaît jamais vraiment dans mes tests de profilage ces jours-ci. Oui, il n'est pas efficace au maximum, mais il est assez proche tout en conservant une lisibilité et une facilité d'utilisation.

@bhouston Compris . Merci beaucoup pour l'explication! 😊

Je voulais juste suivre le sujet. Mais je veux revenir de importing function vs importing classes au sujet de la résolution de cyclic dependencies . (De plus, je ne vois pas pourquoi import { someFunction } from 'SomeModule' est moins maintenable que import SomeClass from 'SomeModule' , mais ce n'est certainement pas le sujet de ce problème/conversation.

Pour résoudre la dépendance cyclique, il serait possible de placer la fonctionnalité dans une classe distincte. Vous pouvez attacher une méthode multiplyMatrices à la classe Math ou créer une classe multiplicateur qui a la méthode multiplyMatrices . Mais comme je l'ai déjà dit, je ne sais pas si nous devons supprimer les dépendances cycliques. Si la décision est de ne pas les supprimer, je pense que ce problème pourrait être proche 😃

Après avoir résolu #19137, cela peut être fermé maintenant 🎉.

@ Mugen87 ouah, 5 ans ! félicitations pour l'avoir enfin frappé :fire: :clap:
Je me suis beaucoup amusé à faire ces graphiques à l'époque :smile_cat:

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

clawconduce picture clawconduce  ·  3Commentaires

seep picture seep  ·  3Commentaires

konijn picture konijn  ·  3Commentaires

yqrashawn picture yqrashawn  ·  3Commentaires

Horray picture Horray  ·  3Commentaires