question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Refactor to use polymorphism?

See original GitHub issue

I hope this will be received in the spirit of positivity and contribution.

Have you considered refactoring threejs to use more polymorphism?

this kind of code

			if ( object.isGroup ) {

				groupOrder = object.renderOrder;

			} else if ( object.isLOD ) {

				if ( object.autoUpdate === true ) object.update( camera );

			} else if ( object.isLight ) {

				currentRenderState.pushLight( object );

				if ( object.castShadow ) {

					currentRenderState.pushShadow( object );

				}

			} else if ( object.isSprite ) {

				if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {

					if ( sortObjects ) {

						_vector3.setFromMatrixPosition( object.matrixWorld )
							.applyMatrix4( _projScreenMatrix );

					}

					const geometry = objects.update( object );
					const material = object.material;

					if ( material.visible ) {

						currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );

					}

				}

			} else if ( object.isImmediateRenderObject ) {

				if ( sortObjects ) {

					_vector3.setFromMatrixPosition( object.matrixWorld )
						.applyMatrix4( _projScreenMatrix );

				}

				currentRenderList.push( object, null, object.material, groupOrder, _vector3.z, null );

			} else if ( object.isMesh || object.isLine || object.isPoints ) {

				if ( object.isSkinnedMesh ) {

					// update skeleton only once in a frame

					if ( object.skeleton.frame !== info.render.frame ) {

						object.skeleton.update();
						object.skeleton.frame = info.render.frame;

					}

				}

I was always taught in computer programming classes, tutorials, etc. is kind of considered bad practice. Basically checking for a type of class is a sign that the code should be using polymorphism.

Instead each class would have some function that is appropriate for calling there. Let’s call it handleProjection since the code above is from WebGLRenderer.projectObject

The code above could change to just this

 	object.handleProjection( currentRenderState, camera, ... );

Inside Group it might have

class Group {

	handleProject( currentRenderState, camera ) {

		currentRenderState.setGroupOrderer( this.groupOrder );  

	}

}

for other classes, just pseudo code

class LOD {

	 handleProject( currentRenderState, camera ) {

		if ( this.autoUpdate === true ) this.update( camera );

	}

}

class Light {

	handleProject( currentRenderState, camera ) {

		currentRenderState.pushLight( this );

		if ( this.castShadow ) {

			currentRenderState.pushShadow( this );

		}

	}

}

class Sprite {

	handleProject( currentRenderState ) {

		if ( ! this.frustumCulled || currentRenderState.frustum.intersectsSprite( this ) ) {

			if ( currentRenderState.sortObjects ) {

				_vector3.setFromMatrixPosition( this.matrixWorld )
					.applyMatrix4( currentRenderState.projScreenMatrix );

			}

			const geometry = this.update( this );
			const material = this.material;

			if ( material.visible ) {

				currentRenderState.currentRenderList.push( this, geometry, material, currentRenderState.groupOrder, _vector3.z, null );

			}

		}

	} 

}

class ImmediateRenderObject {

	handleRender( currentRenderState ) {

		if ( currentRenderState.sortObjects ) {

			_vector3.setFromMatrixPosition( this.matrixWorld )
				.applyMatrix4( currentRenderState.projScreenMatrix );

		}

		currentRenderList.push( this, null, this.material, currentRenderState.groupOrder, _vector3.z, null );

	}

}

class SkinnedMesh {

	handleProject( currentRenderState )
			// update skeleton only once in a frame
			
			const info = currentRenderState.info;

			if ( this.skeleton.frame !== info.render.frame ) {

				this.skeleton.update();
				this.skeleton.frame = info.render.frame;

			}
			
	}

}

etc.

Similarly, any renderer specific things should arguably be also moved into polymorphic classes so that the same code works with a WebGL renderer vs a WebGPU renderer without any if (type-of-class-is-x) statements.

AFAIK this is how Unity, Unreal, and most AAA 3D engines work. Not this exact way. Only in that they aren’t littered with if statements checking for various classes.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
mralephcommented, Aug 14, 2020

@looeee

Sorry to budge in. Not to tell anybody how the code should be structured, but to correct what seems like a misunderstanding.

It depends on the language. In JavaScript, if you want maximum performance and your code could end up on a hot path, monomorphism is required. You can read more about this here:

https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html

The code above is, in fact, not entirely monomorphic. More specifically all if-conditions are polymorphic (or most likely megamorphic) because they observe objects of different shapes. The code within if-blocks is monomorphic.

If you were to use virtual dispatch you would move from multiple polymorphic property loads to a single polymorphic call, which most likely would be a performance win. (but that depends on some factors).

0reactions
looeeecommented, Aug 15, 2020

I think the poly/monomorphism performance issue is not relevant in this situation, it’s a real thing but has to do more with function caches, not if/else blocks.

Yeah, looking at the code a bit more deeply I was thrown off by this being referred to as a mono/polymorphism issue. @mraleph, thanks for the correction, and @samanthajo2 sorry for being dismissive.

It’s probably better to frame this as conditionals vs dynamic dispatch, and yes, plenty of tutorials do indeed tell you to prefer the latter. The disadvantage is less readable code but the performance increase might be worth it (or might not, I’m not familiar enough with the highlighted section of code to make a guess).

However, as @mrdoob says, we have bigger fish to fry right now with the conversion to classes. Maybe we can reconsider this once that task is done?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Refactoring: Replacing Conditional with Polymorphism
Note: This refactor that was done is called Replace conditional with Polymorphism (pretty easy to remember the name huh?)
Read more >
Polymorphism Part 2: Refactoring to Polymorphic Behavior
One of the most important, but overlooked refactoring strategies is converting logic branches to polymorphic behavior. Reducing complicated ...
Read more >
Replace Conditional with Polymorphism - Refactoring
Replace Conditional with Polymorphism. open in web edition · How do I access the web edition? refactorgram. switch (bird.type) { case 'EuropeanSwallow': ...
Read more >
Refactoring Your Switch to Ad-Hoc Polymorphism for Better ...
I've mentioned this a tonne already, and this won't be my last, but branching on discrete values using switch cases and if-else statements ......
Read more >
Refactoring conditional statements using polymorphism in C# ...
Refactoring conditional statements using polymorphism in C# ... Suppose you have a conditional statement such as a switch or if-else that performs ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found