This content originally appeared on DEV Community and was authored by Joe Pea
Hello! :)
A while back, I started a port of Three.js from JavaScript to AssemblyScript (an awesome TypeScript to WebAssembly compiler):
GLAS
WebGL in WebAssembly with AssemblyScript.
This is a work-in-progress port of Three.js, a JavaScript 3D WebGL library, into AssemblyScript.
Motivation
It'd be sweet to have a high-performing WebGL engine that runs in the web via WebAssembly and is written in a language that web developers are already familiar with JavaScript in the form of TypeScript (a superset of JavaScript with types).
Enter AssemblyScript, a toolchain that allows us to write a strictly-typed subset of TypeScript code and compile it to WebAssembly (an assembly-like language representing machine code) for speed.
Status
⚠️ ALPHA STATE
The project is currently in its very early alpha stages. We have an amazing group of programmers building the initial ASWebGLue library. This library allows AssemblyScript programs to call the browser's underlying WebGL interface. This is required before we are able render anything to the screen. A…
I was originally interested in porting Babylon 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..
To get an understanding of this problem with respect to Babylon's codebase, take look at the import statements in Babylon’s scene.ts
:
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
:
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).
This content originally appeared on DEV Community and was authored by Joe Pea
Joe Pea | Sciencx (2021-05-26T21:31:35+00:00) Loosely coupled code: Babylon vs Three.js. Retrieved from https://www.scien.cx/2021/05/26/loosely-coupled-code-babylon-vs-three-js/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.