At Leap Motion, we often create tools for our own internal development, and today we’re really glad to share a new tool with you. We’re releasing Autowiring – our inversion-of-control framework for C++11 – to the community under an Apache 2.0 license.
Autowiring has been a year and a half in the making, and we’re excited to release it to the community as a general-purpose resource. It addresses the problem of object, thread, and application lifetime management; makes it easier for you to configure and compose components; and frees you from the burdensome restriction of always locating your algorithms close to your data. The whole project is well-commented and available on GitHub, and as the project sees more use, documented on the GitHub wiki.
What is Autowiring?
Autowiring is a C++ inversion-of-control framework that provides a declarative way to manage state and resources through dependency injection. Simply declare what you need and Autowiring will manage creation and connectivity. It’s the first library of its kind built expressly on C++11 feature sets and can be used with any C++11 project.
We created Autowiring to solve plumbing problems with our code. Instead of maintaining complex and artificial hierarchies of objects directly from a universal root object, you specify the dependencies your component has, and they are automatically connected. For dynamic data, you specify the types of your inputs and outputs, and the AutoFilter network will connect your components together for you. Dependency injection is used to supply dependencies declared by components, rather than having components attempt to find those dependencies on their own (also known as the Hollywood principle – “don’t call us, we’ll call you”).
Better lifecycle management
We’ve used Autowiring to help us create components that need to interact with other parts of our core tracking software. Sometimes the components in the source code are close to the data that they need – making it easy for them to work – and sometimes the data is very far away.
For instance, the hardware image that comes off our sensors is briefly available when it streams off the device. Afterwards, image processing and tracking algorithms are applied. But if a component at the end of the process needs to use some of the data that’s available in the beginning, we run into serious problems.
Under a traditional control framework, this would involve plumbing the data through every single module between the source and destination. This is unsustainable, since pieces of code that were formerly well-defined and modularized need to start worrying about things completely unrelated to their actual purpose. This increases the overall complexity of the code and makes it harder to maintain over time.
DRY + cleaner codebase geography
Don’t Repeat Yourself. It’s one of the most important laws of software engineering, but the problem of distance often forces engineers to violate it for the sake of performance. In new projects, engineers can place algorithms quite close to the data they operate on. Over time, however, layers are added. Codebases become more sophisticated. Services offered by lower-level systems are less and less available to entities further up in the hierarchy. At some point, something only available at a very low layer will be needed by an algorithm higher up in the stack.
A developer doesn’t have many choices at this point. If it’s an algorithm they need, the developer might copy-and-paste. In cases where it’s data, they might invert an abstraction. If neither option is available, extensive plumbing might be required, which violates the separation of concerns.
So, rather than putting algorithms together just because they happen to operate on the same data, we make it very easy for any algorithm to get the data or other dependencies it wants. This frees us to put algorithms closest to other, similar algorithms. When similar code is put together, it makes it much easier to reason about the system it describes. Better for your sanity, too.
Building concurrent systems
In addition to object lifetime management, Autowiring also supports thread lifetime management, a declarative concurrency model, and a built-in web-based profiling tool. You can build concurrent systems and also profile them in the same package. Autowiring allows threads to be attached to Autowiring contexts and managed in the same way as other objects. Users of the more popular Spring and Guice IoC frameworks will be familiar with the idea of injecting objects into a user’s session; Autowiring extends this concept to allow you to inject entire threads.
Of course, we all know that computer cores aren’t getting any faster and parallel processing is becoming increasingly necessary. People who are writing in C++ are often acutely aware of this limitation. By allowing threads to be attached directly to an Autowiring context, Autowiring makes it easier to build systems that require concurrent message passing – so you can split two components into two different thread domains just by implementing an interface, or inheriting one of Autowiring’s purpose-built mix-in types.
What do you think?
Getting a project ready for open source takes time and resources, but we believe that Autowiring will be a valuable resource management tool for any C++ project. The whole project is available on GitHub, with a readme for bleeding-edge DIYers as well as installation packages on our releases page. Once you have it installed, you can use it in your favorite C++ build environment. To bring it into your project, simply include
Autowiring/Autowiring.h, and you can start using the calls right away.
We’d love to get your feedback on this release! Let us know what you think and feel free to make your own branches and pull requests.