Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


Hodgman

Member Since 14 Feb 2007
Online Last Active Today, 05:02 AM

#5195581 Space terrorists (not exactly)

Posted by Hodgman on 30 November 2014 - 05:00 PM

Now I want to understand them :)
1- why are they doing it?
2- how they are getting funding?
3- where they keep the ships and why imperial security can't track these easily?
4- do population supports them, if yes to what degree (what if they are usurpers or radicals or some sick sect no sane person supports)?
5- how they call themselves?

1 always has to be to achieve a political aim through fear, otherwise they're not really terrorists ;)

Some tropes:
1- your empire is occupying another state, who wants their independence back.

2- individual freedom fighters using their own personal resources.

3- they're private ships until the moment they're used for an attack (revealing the owner).

4- there's many sympathisers against the occupation, but not publicly because no one supports the killing.

5- (occupied state)-freedom/liberation/democratic/republican-forces/army/league... Etc...



1- your governance is seen to be at odds with individual freedom, leading to rebellion.

2- Foreign anti-empire enemies use their intelligence services to make contact with cells of sympathisers and to covertly smuggle funds/assets/training, or overtly provide aid packages to areas under attack by your anti-rebellion forces.

3- They're innocent ships that have been hijacked - hijacking airliners has always been a powerful terrorism trope.
Or, They're your ships, stolen from your bases that were reached by the rebellion.

4- it's hard to tell. All the universe's media is only publishing propaganda. There's support on both sides, split largely by geography (distance to your capital/strongholds).

5- The general people's congress, the transitional council, (season/landmark)-(peoples) e.g. sovereign-square martyrs.



1- Your say: because they hate freedom, they're a death cult, they're murderous madmen. Really: because they give you an excuse to weild an iron fist. Whenever there's unrest at home or your popularity drops, have your secret police blow up a Cafe housing some of the rabble raisers (while blaming the terrorists). Two birds with one stone!

2- the 'black income' of your own intelligence services. Drug running, extortion, etc...
If your own government has no transparency, then you can find them directly.

3- inside your own secret internal security bases. The anti-terror squads are lower ranking than internal security.

4- you run honeypot operations, having security pose as supporters to find actual supporters, so you can send them to the gulags.
The citizenry just wants their local Cafe to stop being nail-bombed, so they support any action you make.

5- you give them an Emmanuel Goldstein like figurehead for people to direct their hatred at, and give the group a foreign name similar to an actual enemy culture, to encourage nationalism.


#5195181 Putting a calculation in the vertex shader vs. pixel shader

Posted by Hodgman on 28 November 2014 - 07:42 AM

lightDistance itself isn't linear though (abs(sqrt(xx+yy+zz))).

The easily mentally verifiable example I use is a massive triangle, say the size of a football field with me (a small camera) and a small light standing in the middle of the triangle.

Using vertex based calculations, maybe it's 50m to each vertex. Interpolate these 3 values and we get an interpolated result of 50m.
However, I'm actually standing 1m above the triangle...
Obviously vertex calculations plus interpolation are not correct for distance(as earlier- distance is not a linear function)


#5195108 Debugging a blank view, does order matter?

Posted by Hodgman on 27 November 2014 - 11:11 PM

There's another amazing debugger called renderdoc that may be of use.

 

Also, make sure you're creating your device with the D3D11_CREATE_DEVICE_DEBUG flag set during development/testing -- this will make D3D emit useful warnings to the debug output window if you're doing anything wrong. Also make sure you check the HRESULTs of everything to make sure there's no failures being ignored.

 

The order of your "Then:" calls don't matter, except that all of them must come before the Draw call.

e.g.

The IA calls can occur in any order - these are setting different values for vertex input assembly.

The VSSetConstantBuffers call could come before OR after the VSSetShader call -- the device context has a certain number of VS-constant-buffer slots. When changing the shader program, the slots aren't affected.

OMSetRenderTargets could happen at any time, it just specifies where the next draw calls will be drawn to.

UpdateSubresource could come before/after VSSetConstantBuffers. The former call memcpy's bytes into a buffer object. The latter places a pointer to a buffer object into a 'slot', for use by a shader program.




#5195101 Multiple Inheritance or lots of pointers?

Posted by Hodgman on 27 November 2014 - 10:23 PM

Look up "composition vs inheritance".

It's a general rule of OO to default to using composition, but use inheritance when necessary.

 

[rant]

In the 90's, lots of games had:

Entity <- MeshEntity <- AnimatedMeshEntity <- FlyingAnimatedMeshEntity

etc...

which is an abuse of inheritance.

The proplem becomes apparent when you later want to make a FlyingMeshEntity, but have to reimplement the flying logic now...

 

The solution to this is to go back to your OO textbooks and relearn with an eye towards composition.

 

Many of those game programmers who wrote this bad code in the 90's have (ignored their lack of knowledge regarding OO and) just decided that this means that OO is broken, and moved on to the next fad, which seems to be "ECS", a fancy pattern for implementing composition-based entities.

I don't mean to knock ECS, but you should learn to use composition alone first, before trying to design yourself an over-engineered framework designed to make composition easier for you biggrin.png

[/rant]

 

P.S. you can still use Entity as an interface that you inherit from, but also use composition as well, e.g.

struct PhysicalMeshInstance // simple component to tie a visual and physics mesh instance together, can be re-used by many different entities
{
  PhysicalMeshInstance(MeshAsset& m) : visual(m), physics(m) {}
  MeshInstance visual;
  PhysicsBody physics;
};
 
struct AnimatedProp : public IEntity // simple entity for animated models
{
  AnimatedProp(MeshAsset& m) : mesh(m), skeleton(mesh.visual) {}
  PhysicalMesh mesh;
  SkeletonInstance skeleton;
 
  //implementation of the IEntity interface - delegate to the components
  virtual void Update() { skeleton.Update(); }
  virtual void Draw( vector<IDrawable*>& vec ) { vec.push_back(&mesh.visual); }
};



#5194868 OpenGL 5 - Release?

Posted by Hodgman on 26 November 2014 - 05:53 PM

OpenGL 5 was announced a few months ago??

 

[edit] I assume you're thinking of this: https://www.khronos.org/news/press/khronos-group-announces-key-advances-in-opengl-ecosystem

 

"Next Generation OpenGL" will be a complete rewrite of OpenGL from scratch... so hopefully it will be called "OpenGL NG" and not "OpenGL 5".

 

Current top-of-the line GPUs will be DX12/Mantle compatible (though only AMD are writing Mantle drivers at the moment). So I would assume OpenGL NG will be based around the same set of hardware features as DX12/Mantle GPUs.




#5194867 Constructors, Factory Methods and Destructor Questions

Posted by Hodgman on 26 November 2014 - 05:51 PM

Small point of order - modern C++ lets you delete functions with "=delete". This implements "non-copyable" in a much cleaner way, and one that the compiler can catch at compile time, rather then link time.

You get a compile time error with the older method as well, because the (unlinkable) functions are private.
 

What does C# have that makes composition easier than in say C++?

I wasn't saying that C# is better than C++ in this regard -- I mentioned C#'s support for composition because the OP stated that C# seems to thrive on inheritance.
...but anyway, it depends which C++ you're talking about smile.png
C++ now has first-class-functions (via the standard library, not a language feature) and lambdas, but C# had them long before they were standardized in the C++ world.
Before C++ 11, you had to either use boost::function and/or roll your own lambdas/closures using a rediculous amount of boilerplate code (usually a struct with a constructor that captures the locals explicitly, and an operator() so it acts like a functor).
C#'s generics aren't equal to C++'s templates. In a lot of ways, templates are way more powerful... but generics also have some interesting features similar to the proposed C++ 'concepts' feature. Plus C#'s compilation system is modern, letting you actually use generics without worrying about "moving code into a header file", ruining compile times, ruining inter-system coupling, etc...
Both generics and C#'s actual duck typing support can be used to reduce the amount of boilerplate 'adapter pattern' style code that's required when gluing self-contained components into larger systems.




#5194723 c++ directX app crashes

Posted by Hodgman on 25 November 2014 - 11:03 PM


You can download that dll here: http://es.dll-files.com/msvcr100d.dll.html
Never download DLLs from untrusted sites like this, unless you're happy to install a virus onto your system!!

Always use the authentic redistributable installers (which should have been bundled with the program that is generating this error), such as:

http://www.microsoft.com/en-us/download/details.aspx?id=8328




#5194706 The "action" systems that 2D game frameworks all now have...

Posted by Hodgman on 25 November 2014 - 07:18 PM

Can you register for OnUpdate/etc events?

Lots of game engines I've used in the past were entirely event based, but a damn lot of stuff always ended up an Entity's called-once-every-frame OnUpdate event.




#5194674 real time reflections from arbitory surfaces.

Posted by Hodgman on 25 November 2014 - 03:39 PM

Moriffy, you can't just post a link by itself. You also need to post some words/thoughts alongside the link.

Did you create these videos?
Do you want feedback on them?
Do you want to discuss the technique used in them?
Are you just impressed/excited by the videos?
Is the video demonstrating a product or a generally known technique?
How does it work?

What are the pros and cons of this technique?
Is it general ray-tracing?
Does it only work with cubes?
What kinds of BRDFs does it support?

Can we use this technique?

In other words, what kind of replies are you expecting...?




#5194500 Constructors, Factory Methods and Destructor Questions

Posted by Hodgman on 24 November 2014 - 05:25 PM

@Hodgman: The idea of having leaner constructors make quite a bit of sense. I usually try to do lots of initial setting stuff in my constructors, and file loading. I should probably move the file loading to a factory.

I left it at 'people will argue either way', but I personally am a fan of complex constructors happy.png
For a general purpose file loading library, yeah, I'd prefer a factory function that can either return a file object, or return null/error.
 
But for a game asset loading library, there's no errors that can occur -- If an asset is missing from the game directory, you blame the user for deleting your data files, and then you crash. So I'd have no issues at all with the constructor of a GameAssetFile class doing complex work like obtaining file pointers, kicking off asynchronous HDD reads, etc...

Another downside to be aware of though, is when you use a complex constructor (and no default constructor), you're limiting your class from being usable in some places, e.g. std::vector<GameAssetFile> will no longer work (but std::vector<GameAssetFile*> will).

Some examples for the original questions; I have these two helper classes:

class NonCopyable
{
public:
	NonCopyable(){}
private:
	NonCopyable( const NonCopyable& );
	NonCopyable& operator=( const NonCopyable& );
};

class NoCreate
{
private:
	NoCreate(){}
	NoCreate( const NoCreate& );
	NoCreate& operator=( const NoCreate& );
};

I use NonCopyable when I don't have a need for a class to be able to make copies of itself and/or when implementing copying would be too complex. I'm a big fan of YAGNI and KISS, so if I don't need a copy operator, I don't write it.

class SomeComplexThing : NonCopyable
{
public:
  SomeComplexThing(const char* filenameToOperateOn);
  ~SomeComplexThing();
};

Without inheriting from NonCopyable, the above class would be in breach of the rule of three. Inheriting NonCopyable satisfies the rule, while not requiring me to actually implement copying. If a user tries to copy an object of this class, they'll get a compile time error saying it's not allowed.
 
NoCreate is a lot more esoteric. I use it as a helper for a type of non-polymorphic interfaces, something similar to the PIMPL pattern.

//header
class Doodad : NoCreate
{
public:
  int GetHealth();
  std::string GetName();
};
//user cannot create/destroy Doodads, only get pointers to the ones owned by the complex thing
class SomeComplexThing : NonCopyable
{
public:
  SomeComplexThing(const char* filename);
  int GetDoodadCount() const;
  Doodad* GetDoodadByIndex(int i) const;
private:
  void* buffer;
};

//cpp file
SomeComplexThing::SomeComplexThing(const char* fn) : bufffer(LoadFile(fn)) {}
SomeComplexThing::~SomeComplexThing()                      { FreeFile(buffer); }

struct DoodadFile //implementation of the Doodad interface
{
  int count;
  struct Item
  {
    int health;
    char name[64];
  } items[];
};

int SomeComplexThing::GetDoodadCount() const
{
  DoodadFile* d = (DoodadFile*)buffer;
  return d->count;
}
Doodad* SomeComplexThing::GetDoodadByIndex(int i) const
{
  DoodadFile* d = (DoodadFile*)buffer;
  if( i >=0 && i < d->count )
    return (Doodad*)&d->items[i];
  return 0;
}

int Doodad::GetHealth()
{
  DoodadFile::Item* self = (DoodadFile::Item*)this;
  return self->health;
}
std::string Doodad::GetName()
{
  DoodadFile::Item* self = (DoodadFile::Item*)this;
  return std::string(self->name);
}

 

I'd like to mention is that it sounds like inheritance should be used sparingly in C++ whereas other languages like Objective-C and C# thrive off of it.

I wouldn't say "sparingly". Inheritance is used alot, but it's just not the first tool you reach for.

 

I would say that in every OO language, inheritance should be use sparingly, but unfortunately many people suffer from inheritance-addiction.
As far as I'm concerned "prefer composition over inheritance" is one of the core rules of OO design. I won't rant again because I just posted one here laugh.png
But for what it's worth, C# has good support for inheritance and amazing support for composition as well.




#5194380 A few questions about viewports

Posted by Hodgman on 24 November 2014 - 04:56 AM

That's pretty much right except the world/view/proj matrices map into the -1 to 1 range (a.k.a. Normalised device coordinates), and then yeah the viewport remaps into pixel coordinates.

Viewport coords are in pixels, yeah, so they're independent of the render target size.
However, D3D9 has a quirk where whenever you set a render target, the viewport is automatically changed to be the same size as that full texture's size... So always remember to set the viewport after you set your target (and re-set it if you change targets and want your viewport to persist).


#5194358 Time correction when changing a value based on Time passed.

Posted by Hodgman on 23 November 2014 - 10:19 PM

Alright, in our game whenever the player jumps we calculate when he should start falling based on how long he has been jumping for.  This causes issues when the game is running at the fastest setting, since the deltaTime is added more frequently. I tried normalizing the vector returned by muliplying by deltaTime, but that just made the jump return a very tiny movement vector everytime. So, how could I go about normalizing the deltaTime?

Here's our code:

private Vector3 OnPress()
{
	jumpTimer = 0;
	jumpVelocity = new Vector3(0f, 11.5f, 0f);
	return jumpVelocity;
}

private Vector3 Rising()
{
	jumpTimer += Time.deltaTime;
	jumpVelocity = (gravity * jumpTimer) + jumpVelocity;
	return jumpVelocity;
}

private Vector3 Dropping()
{
	jumpTimer += Time.deltaTime;
	jumpVelocity = gravity * jumpTimer;
	return jumpVelocity;
}

private Vector3 Landing()
{
	jumpVelocity = Vector3.zero;
	jumpTimer = 0f;
	return jumpVelocity;
}
I believe the issue is within Dropping and Rising. Thanks.

Rewriting your current code, it's pretty much this:
onJump:
	acceleration = 0;
	velocity = (0, 11.5, 0);
rising:
	acceleration += deltaTime * gravity;
	velocity += acceleration; //n.b. deltaTime not used here!!!
dropping:
	acceleration += deltaTime * gravity;
	velocity = acceleration; //n.b. = used, not += ???
onLand:
	velocity = (0,0,0);
	acceleration = 0;
...which shows that your equations of motion are wrong. Velocity is updated without any respect to delta time, which is why it will vary greatly with framerate.


Also, I'm not sure why rising and dropping need different update functions. You should be able to just use something like this:
onJump:
	acceleration = 0;
	velocity = (0, 11.5, 0);
rising:
dropping:
	acceleration += deltaTime * gravity;
	velocity += deltaTime * acceleration;
onLand:
	velocity = (0,0,0);
	acceleration = 0;
Or an alternate version:
onJump:
        onGround = false;
	impulse = (0, 11.5, 0);
onLand:
	onGround = true;
	impulse = (0, -velocity.y, 0);
update:
        if( !onGround )
		acceleration += gravity * deltaTime ;
	velocity += acceleration * deltaTime + impulse;
	impulse = (0,0,0);
^^Both of those versions update velocity using deltaTime, so they should be more stable. However, they will still give slightly different results with different frame-rates, due to them being an approximate numerical integration of the motion curve.


To solve that, you can either use a fixed timestep (as mentioned by everyone above), or you can use a version that is based on absolute time values, instead of delta time values, which makes it perfectly deterministic and always works the same regardless of framerate:
onJump:
        onGround = false;
	initialJumpHeight = height;
	initialJumpVelocity = 11.5;
	timeAtJump = timeNow;
onLand:
	onGround = true;
update:
	if( !onGround ) {
		timeSinceJump = timeNow - timeAtJump;
		//motion under constant acceleration: o' = o + ut + att/2
		//(o'=new pos, o=initial pos, u=intiial velocity, t=time since initial conditions, a=acceleration)
		height = initialJumpHeight + initialJumpVelocity*timeSinceJump + 0.5*gravity*timeSinceJump*timeSinceJump;
	}
	return height;



#5194347 Is it possible to do Batch Rendering with 3D skeletal animation data?

Posted by Hodgman on 23 November 2014 - 07:34 PM

If I want each stick to be animated and positioned correctly, I'd need three 4x3 transformation matrices to define how to move and rotate each of the sticks in 3D space.  If I do what you propose, I'd have 3 indices per vertex, and an array containing three 4x3 matrices?  This doesn't scale very well as a model will have 100's of vertices which means 100's of 4x3 matrices.

I don't understand how you've come up with three in the bolded bit. Each stick only has two bone (head/foot), so each stick has two matrices. Each vertex also only has one bone-index because it's either connected to the head, or to the feet.
 
 
It doesn't matter how many vertices are in the feet/head. Per object, you have one 'feet' transform and one 'head' transform.
 
Transform buffer: {Head0, Feet0, Head1, Feet1, Head2, Feet2...}
 
Vertex Buffer if the head was made up of 2 verts and the feet also of two verts:

{
//stick 0's verts
  {pos={a,b,c},uv={d,e},bone={0/*aka Head0*/}},
  {pos={f,g,h},uv={i,j},bone={0/*aka Head0*/}},
  {pos={k,l,m},uv={n,o},bone={1/*aka Feet0*/}},
  {pos={p,q,r},uv={s,t},bone={1/*aka Feet0*/}},
//stick 1's verts
  {pos={u,v,w},uv={x,y},bone={2/*aka Head1*/}},
  {pos={z,A,B},uv={C,D},bone={2/*aka Head1*/}},
  {pos={E,F,G},uv={H,!},bone={3/*aka Feet1*/}},
...
}

In the vertex shader, you then do something like:

  int boneIndex = vertex.bone;
  Vec4 transform0 = TransformBuffer.Load(boneIndex*3+0);//index*3 because we have 3 Vec4's per transform
  Vec4 transform1 = TransformBuffer.Load(boneIndex*3+1);
  Vec4 transform2 = TransformBuffer.Load(boneIndex*3+2);
  Mat4 transform = Mat4( transform0, transform1, transform2, vec4(0,0,0,1) );
  Vec3 worldPosition = mul(transform, Vec4(vertex.position,1) );

Then as an extension to this, you can get "skinning" (soft transitions between bones) by using more than one bone index per vertex.
e.g. A vertex that's 75% controlled by the head bone, but 25% by the feet bone:
  {pos={a,b,c},uv={d,e},bones={0/*aka Head0*/, 1/*aka Feet0*/}, weights={0.75,0.25}},

Then a VS that loads multiple bone indexes and blend weights for each one.

  int boneIndex0 = vertex.bones.x;
  Vec4 transform0_0 = TransformBuffer.Load(boneIndex0*3+0);
  Vec4 transform0_1 = TransformBuffer.Load(boneIndex0*3+1);
  Vec4 transform0_2 = TransformBuffer.Load(boneIndex0*3+2);

  int boneIndex1 = vertex.bones.y;
  Vec4 transform1_0 = TransformBuffer.Load(boneIndex1*3+0);
  Vec4 transform1_1 = TransformBuffer.Load(boneIndex1*3+1);
  Vec4 transform1_2 = TransformBuffer.Load(boneIndex1*3+2);

  Vec4 transform0 = transform0_0 * vertex.weights[0] + transform1_0 * vertex.weights[0];
  Vec4 transform1 = transform0_1 * vertex.weights[0] + transform1_1 * vertex.weights[0];
  Vec4 transform2 = transform0_2 * vertex.weights[0] + transform1_2 * vertex.weights[0];

  Mat4 transform = Mat4( transform0, transform1, transform2, vec4(0,0,0,1) );

p.s. the above code does horrible linear blending of matrices, which doens't produce very good quality. Often animation systems will use a quaternion + a vec3 scale + a vec3 position, blending them individually, and then using those blended results to construct a Mat4x4.
p.p.s. Half-Life 1 in 1998 was one of the first games I know of that pioneered "skinned animation" and it's been the defacto standard character animation technique ever since. It's common these days to have characters with, say, 10k verts and 50 bone matrices. Nextgen even more like 100k verts and 150 bone matrices.




#5194250 General question: how to keep JavaScript organized

Posted by Hodgman on 23 November 2014 - 06:04 AM

In Java, for example, you would get this result by defining an interface ... In C++ you could create two base classes modules [and] multiple inheritance
...
But those designs are really workarounds for the weakness of the OOP model: you cannot modify objects at run time, except to modify their properties' values.

That's not an issue with OO itself. Firstly it's a side-effect of inheritance-addiction like I mentioned above, and secondly a problem caused by static typing.
 
The reason JS excels here is because it has duck typing, which lets you dynamically use properties without statically defining structures. From a C++/Java-esque background, this seems like "everything is virtual", but of course is so much more than that.

var aDuck = { quack = function(){alert('quack');}; };
var aPerson = { quack = function(){alert('Hello!');}; };
function quack( who ){ who.quack(); }
quack(aDuck);//quack
quack(aPerson);//Hello!

 
C++ only has duck typing inside of templates, e.g.

struct Duck { void Quack() { print("quack"); } };
struct Person { void Quack() { print("Hello!"); } };
template<class T> void Quack( T& object ) { object.Quack(); }
void Test() {
  Duck aDuck;
  Person aPerson;
  Quack(aDuck);//quack
  Quack(aPerson);//Hello!
}

And yes as you mention, at other times where static typing is enforced more strongly, you can instead use interfaces like this in C++ (Java is identical, except the syntax differs):

struct IQuackable { virtual void Quack() = 0; }
struct Duck : public virtual IQuackable { void Quack() { print("quack"); } };
struct Person : public virtual IQuackable { void Quack() { print("Hello!"); } };
void Quack( IQuackable& quackable ) { quackable.Quack(); }
void Test() {
  Duck aDuck;
  Person aPerson;
  Quack(aDuck);//quack
  Quack(aPerson);//Hello!
}

However... that's not the only solution that 'OO' offers. A core rule of OO is that you should default to using composition, and only use inheritance where necessary (Java shits all over this rule, so IMHO I wouldn't choose to recognize it a real OO language until it agrees to an intervention tongue.png).

JS has first-class functions, and C++ almost does (relying on library support over language support) -- which lets you rewrite the above interface/inheritance based solution as a function/composition based one, like this:

typedef std::function<void()> Quacker;
struct Duck { void Quack() { print("quack"); } Quacker GetQuacker() { return std::bind(&Quack, this, _1)); } };
struct Person { void Quack() { print("Hello!"); } Quacker GetQuacker() { return std::bind(&Quack, this, _1)); } };
void Quack( FnQuack& quacker ) { quacker(); }
void Test() {
  Duck aDuck;
  Person aPerson;
  Quack(aDuck.GetQuacker());//quack
  Quack(aPerson.GetQuacker());//Hello!
}

Again, after learning JS properly, trying bringing back all the wonderful new perspectives of it's paradigms back to other languages like C++ biggrin.png

 

C# also does pretty well in this regard. Anonymous functions, closures, delegates, events and generics are great tools to have in your toolbox. In C# 4 there's actually full blown duck typing available! Another way to practice JS-style thinking to to just try using these other tools instead of interfaces all the time in the languages you already know.




#5194249 General question: how to keep JavaScript organized

Posted by Hodgman on 23 November 2014 - 05:37 AM

If you search for "right way to learn JavaScript", there's a lot of decent advice on how not to fall into the abundant traps that exist due to the immense about of bad JS that exists out there.

BTW, js's particular flavour of OO is the prototype paradigm -
http://en.m.wikipedia.org/wiki/Prototype-based_programming

Lastly, the kind of inheritance that C++/Java are renowned for shouldn't actually be very common in good C++ code. This over-use of inheritance is a remnant of the 90's when OOP was a new fad that everyone dived into without actually groking, resulting in lots of bad code, and then lots of people learning from that bad code... Much like JS :)
Writing C++ using composition over inheritance and lots of std::function instead of virtual would be a great eye opener if you're a C++ coder that wants to gain new perspectives on OOP.
Learning JS (properly) will be similarly eye opening.




PARTNERS