Tips and tricks » Method chaining

Little feature helping to reduce typing and encourage best practices.

Method chaining (Wikipedia) is a feature which allows you to chain method calls one after another without repeatedly specifying variable the method is called on. Its primary goal is to reduce unnecessary repeated names, improving code readability.

Magnum uses this feature mainly for configuring OpenGL objects (such as various mesh and framebuffer options, shader uniforms etc.). Because OpenGL was designed with "bind-to-modify" approach, most configuration calls internally need to bind the object first and only after that change the parameters (unless ARB_direct_state_access extension is available to avoid this). To reduce unneeded bind calls, Magnum binds the object only if it is not already bound somewhere. Method chaining encourages you to configure whole object in one run, effectively reducing the number of needed bindings. Consider the following example:

GL::Texture2D carDiffuseTexture, carSpecularTexture, carBumpTexture;

carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256});
carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256});
carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256});
carDiffuseTexture.setSubImage(0, {}, diffuse);
carSpecularTexture.setSubImage(0, {}, specular);
carBumpTexture.setSubImage(0, {}, bump);
carDiffuseTexture.generateMipmap();
carSpecularTexture.generateMipmap();
carBumpTexture.generateMipmap();

This code is written that similar configuration steps are grouped together, which might be good when somebody needs to change something for all three textures at once, but on the other hand the code is cluttered with repeated names and after each configuration step the texture must be rebound to another. With method chaining used the code looks much lighter and each object is configured in one run, reducing count of bind calls from 9 to 3.

carDiffuseTexture.setStorage(5, GL::TextureFormat::SRGB8, {256, 256})
    .setSubImage(0, {}, diffuse)
    .generateMipmap();
carSpecularTexture.setStorage(3, GL::TextureFormat::R8, {256, 256})
    .setSubImage(0, {}, diffuse)
    .generateMipmap();
carBumpTexture.setStorage(5, GL::TextureFormat::RGB8, {256, 256})
    .setSubImage(0, {}, bump)
    .generateMipmap();

Method chaining is not used on non-configuring functions, such as GL::Framebuffer::clear() or GL::AbstractShaderProgram::draw(), as their desired use is commonly as a last step in the chain, after everything else.

Method chaining is also used in SceneGraph and other libraries and in some cases it allows you to just "configure and forget" without even saving the created object to some variable, for example when adding static object to an scene:

Scene3D scene;

(*(new MyObject(&scene)))
    .rotateX(90.0_degf)
    .translate({-1.5f, 0.5f, 7.0f});