Loosely coupled code: Babylon vs Three.js
Tags: javascript, webgl, threejs, babylonjs
Published: 2021-05-26 21:31:35

Hello! :)

A while back, I started a port of [Three.js](http://threejs.org) from JavaScript to [AssemblyScript](https://www.assemblyscript.org/) (an awesome [TypeScript](http://typescriptlang.org) to [WebAssembly](https://webassembly.org/) compiler):

{% github lume/glas %}

I was originally interested in porting [Babylon](http://babylonjs.com) because it is already written in TypeScript, so porting would be easier compared to taking a JavaScript codebase and adding types to it after refactoring the dynamic parts that TypeScript can't handle.

However, when I started to port Babylon classes, I soon realized that the classes in the library are highly coupled: importing Babylon’s `Scene` class means practically importing the whole Babylon library and instantiating many parts of the library (`new This`, `new That`, etc) even if those parts will never be used by your application.

Essentially, with Babylon you want the banana (f.e. `Scene`) and you get the Gorilla and the whole jungle (rest of the Babylon lib).

[High coupling is something to be avoided when possible.](https://en.wikipedia.org/wiki/Coupling_(computer_programming)).

To get an understanding of this problem with respect to Babylon's codebase, take look at the import statements in Babylon’s [`scene.ts`](https://github.com/BabylonJS/Babylon.js/blob/master/src/scene.ts):

```js
import { Nullable } from "./types";
import { Tools } from "./Misc/tools";
import { IAnimatable } from './Animations/animatable.interface';
import { PrecisionDate } from "./Misc/precisionDate";
import { Observable, Observer } from "./Misc/observable";
import { SmartArrayNoDuplicate, SmartArray, ISmartArrayLike } from "./Misc/smartArray";
import { StringDictionary } from "./Misc/stringDictionary";
import { Tags } from "./Misc/tags";
import { Vector2, Vector3, Matrix, TmpVectors, Vector4 } from "./Maths/math.vector";
import { Geometry } from "./Meshes/geometry";
import { TransformNode } from "./Meshes/transformNode";
import { SubMesh } from "./Meshes/subMesh";
import { AbstractMesh } from "./Meshes/abstractMesh";
import { Mesh } from "./Meshes/mesh";
import { IParticleSystem } from "./Particles/IParticleSystem";
import { Bone } from "./Bones/bone";
import { Skeleton } from "./Bones/skeleton";
import { MorphTargetManager } from "./Morph/morphTargetManager";
import { Camera } from "./Cameras/camera";
import { AbstractScene } from "./abstractScene";
import { BaseTexture } from "./Materials/Textures/baseTexture";
import { Texture } from "./Materials/Textures/texture";
import { RenderTargetTexture } from "./Materials/Textures/renderTargetTexture";
import { ImageProcessingConfiguration } from "./Materials/imageProcessingConfiguration";
import { Effect } from "./Materials/effect";
import { UniformBuffer } from "./Materials/uniformBuffer";
import { MultiMaterial } from "./Materials/multiMaterial";
import { Light } from "./Lights/light";
import { PickingInfo } from "./Collisions/pickingInfo";
import { ICollisionCoordinator } from "./Collisions/collisionCoordinator";
import { PointerEventTypes, PointerInfoPre, PointerInfo } from "./Events/pointerEvents";
import { KeyboardInfoPre, KeyboardInfo } from "./Events/keyboardEvents";
import { ActionEvent } from "./Actions/actionEvent";
import { PostProcessManager } from "./PostProcesses/postProcessManager";
import { IOfflineProvider } from "./Offline/IOfflineProvider";
import { RenderingGroupInfo, RenderingManager, IRenderingManagerAutoClearSetup } from "./Rendering/renderingManager";
import { ISceneComponent, ISceneSerializableComponent, Stage, SimpleStageAction, RenderTargetsStageAction, RenderTargetStageAction, MeshStageAction, EvaluateSubMeshStageAction, PreActiveMeshStageAction, CameraStageAction, RenderingGroupStageAction, RenderingMeshStageAction, PointerMoveStageAction, PointerUpDownStageAction, CameraStageFrameBufferAction } from "./sceneComponent";
import { Engine } from "./Engines/engine";
import { Node } from "./node";
import { MorphTarget } from "./Morph/morphTarget";
import { Constants } from "./Engines/constants";
import { DomManagement } from "./Misc/domManagement";
import { Logger } from "./Misc/logger";
import { EngineStore } from "./Engines/engineStore";
import { AbstractActionManager } from './Actions/abstractActionManager';
import { _DevTools } from './Misc/devTools';
import { WebRequest } from './Misc/webRequest';
import { InputManager } from './Inputs/scene.inputManager';
import { PerfCounter } from './Misc/perfCounter';
import { IFileRequest } from './Misc/fileRequest';
import { Color4, Color3 } from './Maths/math.color';
import { Plane } from './Maths/math.plane';
import { Frustum } from './Maths/math.frustum';
import { UniqueIdGenerator } from './Misc/uniqueIdGenerator';
import { FileTools, LoadFileError, RequestFileError, ReadFileError } from './Misc/fileTools';
import { IClipPlanesHolder } from './Misc/interfaces/iClipPlanesHolder';
import { IPointerEvent } from "./Events/deviceInputEvents";
import { WebVRFreeCamera } from "./Cameras/VR/webVRCamera";
```

That's a fairly long list for what a "Scene" represents, and I can see the whole list on my computer screen without scrolling.

In contrast, the Three.js codebase is much more loosely coupled, which to me is very attractive from a code authoring perspective, so I ended up choosing Three.js and accepting that I would perform more porting from JavaScript instead of from TypeScript, because I felt the end result would be cleaner.

As an example of Three’s lose coupling, here are the first few lines of Three’s [`Scene.js`](https://github.com/mrdoob/three.js/blob/dev/src/scenes/Scene.js):

```js
import { Object3D } from '../core/Object3D.js';

class Scene extends Object3D {

	constructor() {

		super();

		this.type = 'Scene';

		this.background = null;
		this.environment = null;
		this.fog = null;
```

Explore Three's code base, and you’ll see that classes try to be minimal and do only one thing well (or representing only one concept well).

After starting the port using Babylon, going back to Three.js felt pleasant due to the loose coupling. The Three.js codebase is clean.

If I had to choose to develop with, or extend from, one codebase or the other, I would choose Three.js because the loosely-coupled organization within the library makes code maintenance and extension easier, while allowing certain parts of the Three library to be used without pulling unneeded dependencies into an application.

---

I wanted to ask the Babylon community if anyone else there feels that Babylon's parts are too tightly coupled compared to, for example, Three.js, but my post was immediately hidden as "spam":

https://forum.babylonjs.com/t/high-coupling-in-the-bablyon-codebase-compared-to-three-js/21156/3

(You might not be able to see it if they permanently delete it).

In that thread, I asked

> Has anyone else (especially on Bablyon’s team) thought about this difference between Babylon and Three.js? What are your thoughts on this? Are there any plans or desires to decouple the Babylon code base?

I was hoping to open a discussion on the topic, hoping it might light a fire for Babylon improvement, for everyone's benefit (having alternatives is always great).