Babylon.js project start-organization

Thx John, but unfortunately doesn’t run through the TS or VUE or other options of setting up your development environment. Presumably, this is all basic b* but it would be a helpful chapter to orient someone with intent, but no prior knowledge. [this is coming from a relative noob, who learned smart contracts in a day with the right tutorials, and did do the obvious searches on Youtube for "what is Vue, TS, … Syntax highlighting with TS haha! --I’m just trying to make sure my end of the dev experience level spectrum is represented-- :slight_smile:

Team, thank you! I did find this last night >>> which was the ultra basic explanation I was hoping for haha

Jason! How are you sir?! I’m back haha I’m back to try and make a spherical HUD experience :smiley:
I’ll search the forum now or create a new thread to ask how I might go about it!

The current project i work on is about 200k lines of code, and i have refactored the entire project a couple times. Its mostly mobx and react. Once to remove all use of the “extends” keyword, and another to upgrade to mobx 6 and removing all decorators. I also removed all react class components and experimented with preact non-compat. The majority of the code lives in a few files 10-20k lines each. Before the first refactor, it was small modular files, but it was simply too difficult to work with. With small files, when you change one file, you have to find every importing file and fix it. You also have to spend a lot of mental effort on context switching. Its impossibly hard to maintain, at least for me.

There are a couple other benefits to single files that i’ve found. One is that the class diagram and outline tools work a lot better, so you can navigate your code using the outline instead of the file system. Secondly, with typescript, you can group types together, either at the top or bottom of the file, which both makes understanding the types easier, and keep functional code closer together.

I do move things out into small files when im iterating on something, i just move it back when im done. Like you said , 200 files is unmaintainable, my point exactly:)

I actually think there is a problem with our tooling. Can you imagine writing a 100 page research paper with every page in a different word doc or pdf? Thats ludicrous right? I also wish there was some tooling around modules and http range requests.

No I don’t, my IDE does that automatically (look for WebStorm, VSCode with Intellicense…). In fact, it automatically imports files for me too as I type a function (95% of the time).

// All imports are created/updated/removed/sorted automatically by IDE
import { ENTRY, PROJECT, PROJECT_UPLOAD_CONFIG } from 'core/project/constants'
import { _SCENE } from 'core/project/definitions'

I’d say there are more downsides than benefits:

  1. No code splitting - users arriving to any page of your site has to download all the code, and wait…
  2. Nightmare to navigate within a file with more than 500 lines of code (it’s called Monolith)
  3. Bloated code - I suspect you either do not write tests, or they are included in your production release
  4. Teamwork unfriendly - new features cannot be merged without conflicts
  5. Hard to find things - only you knows where is what, new people taking over you code have to read the entire 200K lines of code to figure how it works. It’s only good for security through obscurity.
    The list goes on…

In my project I rarely navigate, if I want to edit Mesh for example, I just type Cmd + Shift + O then type mesh, then enter (pretty sure you won’t beat that speed and intuitiveness). Then my Mesh.js file only has mesh code, so I do not get distracted by other things.

My setup gives me the luxury of not having to maintain those things, it’s automated :slight_smile:

I’m just pointing out some of the solutions that have existed for several years now. But you should continue using whatever approach that works for you.

Code splitting works fine, as does finding things. Fixing imports, i meant the methods and properties of imports, not the file path (ie: when you or third parties change the public interface)

Say for example, you wanted to swap out babylon for three in a large app, maybe anywhere from 10-50k lines of code. The very first thing i would do , would be to put everything in 1 file and run an ast check on all the top level accessors to the babylon namespace. Idk how i would do it with hundreds of files, i dont think i could.

It is indeed an example of the need for some common solutions, or the golden channel

The fact that your app has so deeply integrated with Babylon as to have 10-50k lines of code is already worrisome.

In my current app, with over 50k lines of code in total, I only have less than 500 lines of code related to Babylon namespace, and they can be easy traced just a you would in one file, because I proxy all Babylon imports to a single babylon.js file:

/* Core */
export { Engine, } from '@babylonjs/core/Engines/engine'
export { Scene as BabylonScene } from '@babylonjs/core/scene'
export { ArcRotateCamera, } from '@babylonjs/core/Cameras/arcRotateCamera'
export { Camera as Cam, } from '@babylonjs/core/Cameras/camera'
// ...more exports

So I do not see your advantage.

I see it as brittle, because when you put all Babylon stuff into it’s own module, than swap to Three.js, you can create thin wrappers around Three.js to match the same exposed API as Babylon, leading to near zero refactor for the rest of you code!

This is what I did, for example, I rarely use Babylon’s Vector3, instead I have my v3 that wraps Vector3 and converts to right handed system coords. So no matter what engine I swap to, my v3() calls in hundreds of places do not need to change.

reexporting from a common import location is an excellent strategy. the babylon - three conversion was just a theoretical example, not cool to just say oh you have bigger problems and derail the point. anyway, this brings up another point, that is probably controversial. If you don’t import anything beyond the root package identifier (ie: import {Whatever} from “@babylon/core” , not “@babylon/core/deep/imported/thing”) , then you dont actually need the common import location, because you can just alias “@babylon/core” to something else, like the babylonjs package, or the notional three shim. Btw contrary to common belief this doesn’t have any effect on tree shaking, only bundle speed.

Although reimporting from a common location is an excellent mitigation strategy, it has a downside to consider. If you want to publish parts of your code as open source for others to use, you really cant because your code is referencing the internal path, instead of a common path. You could mitigate this by putting your shim code inside a function that takes the deps as a callback. ie:

function createVec3({BABYLON}){
 const {...stuff} = BABYLON
...implementation details
}

Yes, it’s easily possible with modular approach. Here is a hypothetical v3 function that can accept either Babylon or Three.js:

// Vector3 for Right Handed System
export function v3 ([x = 0, y = 0, z = 0] = []) {
  switch v3.Engine.name {
    case 'BABYLON':
      return new v3.Engine.Vector3(x, z, y)
    case 'TREE':
      return new v3.Engine.Vector3(x, y, z)
  }
}

Modular architectures isn’t just about grouping things into folders, it’s a way of thinking, as much as having a light saber doesn’t make one a Jedi, but the Jedi’s way does. Once you master the Modular way, your code becomes poetry.

Thanks for the tip, I was thinking about how to do it with Yuka library to have the main logic independent from rendering.

1 Like

Setting object properties on a function causes a deopt. Better to do it in a callback so v8 can inline

See also:
.Dependency injection - Wikipedia

In the same Wiki article there is also a list of disadvantages of this approach (dependency injection):

  • Creates clients that demand configuration details, which can be onerous when obvious defaults are available.
  • Make code difficult to trace because it separates behavior from construction.
  • Is typically implemented with reflection or dynamic programming. This can hinder automation.
  • Typically requires more upfront development effort.
  • Forces complexity out of classes and into the links between classes which might be harder to manage.
  • Encourage dependence on a framework.

I think only the second point is true, and unavoidable in this case

Hey Jelster! I’m just returning to the topic and noting you mentioned hosting a static BJS website.

I was wondering if there are any “tricks” to setting this up on something like Google Cloud Storage?
I have so far:

  • created a basic BJS HTML using npm and TS setup that I found on the web.
  • I’ve subdomained my website, specifically so that I can connect BJS to the subdomain only.
  • I copied all the folders from my PC onto the Google Cloud Storage bucket,
  • and have set my bucket to “public”,
  • and set the website configuration for my main page as: MyFolderInsideBucket/index.html
  • and presuming this is correct, I’ve hit okay and checked the website.

Unfortunately it’s showing me a black screen.

I tested this with a basic/blank HTML test page and index.html file, and that showed correctly.
Because I’m using BJS, do I need to do something special in this process? Would appreciate any pointers!

EDIT: Seems that if I go directly to the full address in my URL, the BJS website pops up as intended, ie. when visiting: subdomain.mysite.com/subfolder/index.html

But for some reason, setting the main page suffix in Google Cloud Storage as subfolder/index.html doesn’t seem to work.

This is a bit off topic, but any idea on whether subfolders can be accepted? or if i need to change the format I used?

Hello @HirangaG - thanks for asking!

One of the more common default setups for npm/TS is to output the compiled TS (JS) from an npm run build script and such into a /dist folder - that one’s the folder that you want to set as the root of your site or alternatively, the contents of which you’ll copy up to your GCS bucket.

As implied above, this folder is essentially your wwwroot, with an index.html containing the properly linked bundles:
image

There should not be any issues with subfolders, you will need to examine your URL’s and use relative path notation (e.g., instead of subfolder/index.html, which refers to a child of the root folder, or a URL reference pointing to the sibling of whatever folder is the current context - ./subfolder/index.html). I found this particularly tricky when configuring webpack and asset output, so here are two things you should make sure are in your webpack.config.js -

  1. Make sure to specify that asset modules should be output into the assets folder with full file name and extension - similar to this:
output: {
        filename: "js/babylonBundle.js",
        path: path.resolve(appDirectory, "dist"),
        assetModuleFilename: 'assets/[name][ext][query]'
    },
  1. specify a module.rules for content type of asset/resource that matches any asset file types you’re using:
 {
                test: /\.(png|jpg|gif|env|glb|gltf|env|stl|m4a|mp3|css|dds|wav)$/i,
                type: "asset/resource"
  }

HTH!

2 Likes

Just some random thoughts:

@pantrej One important question you forgot: Is the project for a single developer or for a team?

Some other notes:

  • I do not believe asking for frameworks should be a first question. I mean do you require one for your project? Sticking with UI frameworks, is your UI sufficiently complex? Is it a button here and there? Or do you have multiple windows, panels, features, etc? Are you in VR? I think therein lies a general point one can make: to a substantial degree the answers to your questions are provided by your project requirements.

  • RE: avoid spaghetti code. Might be controversial but how about: let it happen then fix it?

  • The single most amazing thing of vanilla Javascript is that you hit F5, wait a few milli-seconds, and your code is build. The more dev layers you have that require transpiling, the more waiting for a ready build.

  • Probably the least most amazing thing of vanilla Javascript is its dynmaic nature. So, for me, for any bigger project, Typescript is a requirement. Not sure about the “linting” complaint. Type annotations are not really that much of extra effort - at least compared to its benefits. One of which would be saving on unit tests. As for the transpile time, eye-balling this one: GitHub - swc-project/swc: Rust-based platform for the Web

  • ECS. Oh yeah. I mean if I tell you, you can have a coconut that is an edible, a blunt weapon AND a throwable, how can you not be conviced :smiley: Anyway, I have only just recently implemented an ECS framework on inventory item level, i.e. drag&drop inventory, with body slots, item effects and actor effects. So the level of complexity is a nightmare. The ECS has substantially decoupled actually a lot of things. The biggest one being that now just a single ECS system takes care of syncing data with the Vue inventory UI. This was a huge mess before. The other huge simplification is that you can pass a single type around.
    The downside however, for me anyways, it is a huge struggle with the type system (here: Typescript). I have to admit I still have to rely on an “any” here and there; e.g. iterate over all entities for serialisation…

2 Likes