Wednesday, August 14, 2019

Making Sense of a Cross-Platform Codebase...




Making Sense of a Cross-Platform Codebase Before Development 
And Other Hilarious Jokes You Can Tell Yourself 

Author: Jacob Pendleton


  Whether you found this post before you started development, three months in, searching for a reason to not give up, or somewhere in between, there are a few mistakes one must get through and a few epiphanies one must experience to be productive in a cross-platform codebase. Myself being mid-development for the GAudio libraries, here are a few things I wish I knew before starting development.

1. How the whole thing compiles on three different OSes


  Before Gateware I had never heard of CMake and hardly knew at all what went into the build process for any project. So here it is. CMake is a program that, once configured, generates any IDE-specific solution so that you, the developer, can open the solution, hit the build button, and it just works regardless of what OS your compiling this jumble of cpps, hpps, mms, hs, or whatever languages and extensions you are using for development.

  Below the hood, this is just the CMake application inside the CMake folder (gateware.git.0/CMake) being run from the command line through a batch file or the OS equivalent. CMake then recursively constructs a tree structure for the project using the CMakeLists.txt files found in each directory and creates a solution for VisualStudio on Windows, Code::Blocks on Linux, and Xcode on Mac. This is how the mac specific hpps and mms are only included in the Xcode solution, etc.

  For platform-agnostic files, you can use #ifdef __APPLE__ for mac specific code, for example, or #ifndef __linux__ if you did not want something to be compiled on Linux. These preprocessor commands are what link the end-users' Gateware interfaces to the correct source code as well.

  This solution is placed in a directory called GW_OUTPUT which is placed outside the repository but references the repository. This makes GW_OUTPUT completely disposable, and you can always generate a new solution by rerunning the setup batch file, and the source code you wrote is preserved.

2. How the documentation works


  Gateware like most libraries is built with a backend and a front end: the source code and the interface respectively. The interface is what the end-user has access to in his project if he #includes a Gateware library, but the end-user may not know how to use every function he has access to. This is of course what documentation is there for. Gateware uses software called Doxygen to turn the raw text documentation found in the interface header files into beautiful, professional Html and LaTeX documentation you would see on MSDN or anything else similar. To open the Gateware documentation, just open Documentation/html/index.html with your preferred browser and voila.

  Doxygen accomplishes this by finding specially formatted comments in the gateware interface h files. The comments appear in a comment block ( /* */ ), begin with a bang denoting the heading ( ! ) and use asterisks for each behavior ( * ). Examples can be found in the interface, online, and in the Gateware docs.

3. The unit tests are your friend—Write them well and use them


  Gateware uses a test-driven development model (TDD) where developers first create an interface, instantiate that interface in each unit test, and require each test to successfully pass a single function. If you got none of that, don't worry, I'll break it down.

  Unit tests are bits of code that you run that reveal if your code did what you wanted it to. They are a great tool for finding bugs, and they can also be used to prevent bugs from ever existing if you use them correctly. Gateware's unit tests use the Catch library to call the REQUIRE and CHECK functions in the tests. These functions both evaluate the passed expression and record the result. If an exception is thrown, it is caught, reported, and counted as a failure. These are the macros you will use most of the time. Require is what we use for the final assertion, and if a require fails, the test is aborted.

  If you are working in an already existing interface, say you're fixing bugs or adding features, the first place to go is the unit test cpp file for that interface. Here you can create a test that has one assertion for the behavior you are trying to create. This test should fail of course, as you have not made the function do anything yet in the source code. Once you run the test and it fails, you can then move on to your function and add the minimum amount of code to make the test pass.

  The unit tests should be created in such a way that each test should stand on its own and contain little to no references to other tests; If you comment out a previous test, it should not break any future tests. Each test follows the template:
  TEST_CASE("unique title", "[function tested]")

  Each test should ideally test exactly one behavior. Unit tests have three steps: Arrange, Act, and Assert. During the arrange phase, you create any objects and parameters needed to test the behavior. You then Act by calling the method you are testing and save its return value. You then finally Assert on that value to test that behavior. In C++, the final step is to clean up any memory that was allocated dynamically to prevent any leaks.

  Code coverage is a term that refers to the amount of code executed over all of your unit tests. Because each test only tests for one behavior, any function with branching logic needs multiple assertions to test each branch. For example: if my constructor checks if nullptr was passed as one of the parameters, even if it throws an invalid argument exception, we need to test that it successfully fails. this is called a negative test case and can be added with a:
  REQUIRE(createObject(nullptr) == GW::INVALID_ARGUMENT)

  With this, we run the branch in our function where nullptr is passed as an argument. In a perfect world, our unit tests reach 100% code coverage. That is to say, each branch in every function has its own test. This ideal is not always reached, but it is common to be held to the standard of 80% code coverage.

  Once you have all your tests written, they not only become one of your most valuable debugging tools but also allow you to write less code and program faster as you add features and functions. This is because TDD mandates you write the minimum amount of code to make your assertions pass, and stops you from getting distracted adding unnecessary infrastructure where bugs tend to fester.

4. How the architecture works: COM and UUIDs


  Interfaces are one of the most fundamental ideas of object-oriented programming. You may have used them in several languages and learned the various syntaxes used in higher-level languages; you may have used them in C++ without even realizing it, or perhaps you're an interface pro. Regardless, interfaces at their core are a way of referring to an object as something interactable without exposing all of its members or functions. Each Gateware library follows this principle, and because of this, the same Gateware code will compile and run on any of the three platforms without any changes.

  The GAudio interface, for example, could be referring to a WindowAppAudio object, a MacAppAudio object, or a LinuxAppAudio object, but a GAudio object always has the same functions available to it, no matter how much you change its various implementations. In many programming languages, GAudio would be defined as interface GAudio; C++, however, has no such type. Instead, it is defined as class GAudio with all of its functions pure virtual.

  This means a GAudio cannot be instantiated and must be created by a factory method instead of something like the following:
  GAudio audio = new GAudio(); //will not compile
Because GAudio has pure virtual functions (and no actual implementation code) we defer the creation of a GAudio to a platform-agnostic CreateGAudio(GAudio **_outAudio)like so:
  GAudio * audio = nullptr;
  CreateGAudio(&audio);
On Windows, this creates a new WindowAppAudio and points GAudio to it, and on Mac and Linux, a MacAppAudio and LinuxAppAudio respectively.

  At the beginning of each Gateware interface, there is a serial number-looking thing defined as a GUUIID (Gateware Universally Unique Interface ID). These perform an important function for Gateware end-users when Gateware releases a new version, as the GUUIID will be different for every new version of an interface. Note that the GUUIID only changes if the interface behavior changes. This means it will not be updated if the Doxygen comments are changed, or any other non-behavioral change.

  GUUIID are just Gateware's name for a UUID (Universally Unique ID), also called a GUID (Globally Unique ID). These have been around a while, and generating a new one is guaranteed to be unique because they are so long. Here's GAudio's current GUUIID: 82DE61C1-C47A-41E5-90BE-C31604DF1140.

  Using a UUID for each interface has been around since Microsoft introduced the COM (Component Object Model) in 1993. The one other COM-like approach Gateware uses is the COM inheritance structure for interfaces. In Microsoft's inheritance structure, all interfaces derived from the IUnknown interface. This allows all interfaces to require certain functionality across every interface in the architecture.

  Gateware's base interface is called GInterface. It is GInterface that contains the request interface function which can be used by the end-user to query for a new interface using the GUUIID of the interface they want, and thereby implement an interface update.

5. How the architecture works part 2: the reference counting system


  There are three more functions in GInterface which are implemented in every Gateware class: GetCount, IncrementCount, and DecrementCount. These three functions form the reference counting system, which you can think of as a garage collection system. Reference counting keeps track of the number of objects and users with pointers to a Gateware object so that when the object's reference count falls to 0, the object deletes itself.

  Normally if all pointers to an object are lost or fall out of scope, there is no way to access that object and delete it, and all dynamically allocated memory is leaked. So when one is done with an object, he calls delete on the pointer and the object is deleted. This is only the case when the object was dynamically allocated, that is, objects where the keyword new is used. With our factory method CreateGAudio, as far as the end-user is concerned, nothing was dynamically allocated. And so we have its count start at one. If the user creates another pointer to the object and it is being referenced from multiple places, the end-user must call IncrementCount.

  To fully understand the reference counting system, we must look at a case where both the user and another process are using a Gateware object. In the audio system, when a new sound is created, the end-user calls audio->CreateSound(&sound). Notice how it is the audio system that creates and has a handle to the sound, but the user also has a handle? This GSound object's count starts at 2. When the user is done with the GSound object and queues the GSound for destruction (DecrementCount), GAudio still has control of the sound, and my attempt to access it with PlayAll, PauseAll, or StopAll. This is why, at a safe point, GAudio iterates through its GSounds and GMusic, disconnects, and decrements the ones that are sitting at a count of 1.

In Conclusion


  If I had known these 5 not so simple things before I started development, it would have saved me entire days of work. I write these so that you can apply them in any cross-platform environment, and have a deeper understanding of whats going on quicker than you ever would have otherwise.



Monday, February 25, 2019

Final official week on Gateware

 02/25/2019

Month 4, Week 3

Author: Devin Wright

 Last week I created the Mac demo for the postmortem, I ended up having to using a separate demo from the one on Linux due to the Linux demo being reliant on X11 and it was more work than it was worth to link in X11 on Mac due to it lacking X11 natively. I also fixed a bug with my circle dead zone calculation, it was missing a check to zero out the final axis values if their magnitude was below the dead zone percentage. This meant the dead zone calculation wasn't eliminating low axis values like is should. Lastly, I fixed a bug reported in GAudio that was overflowing a variable when trying to load in a strangely formatted WAVE file.  When reading in unused sections of the WAVE file the section would be read into a long which was usually enough space for the data. However, with a particular WAVE that a user was trying to use it tried to read 190 bytes into the long and overflowed it. So, rather than reading all of the bytes at once I changed it to loop that reads up to a maximum amount of the size of the long until all of the data is read in. 

This week I am just finishing up documentation and getting the slides ready for my postmortem. I am also looking into a possible bug mention by someone using the GInput library. I have not confirmed it yet, but supposedly if you press the left arrow key the shift key will be reported as pressed. I will end up having to make a small test project to see if I can recreate the bug. 

Monday, February 18, 2019

Code::Blocks and the terror of -l+

 02/18/2019

Month 4, Week2

Author: Devin Wright

 This last week was spent trying to convert an old Windows OpenGL demo to work on Linux for use in my postmortem. While doing this I ran into a lot of linker errors due to ether missing or incorrectly linked libraries. After, fixing most of these errors on the computer running Linux Mint, with some help from Lari, we were left with a linker error where it was trying to link to -l+. However, -l+ was not being included in the projects linker settings, so we tried to create various other project types and were getting the same error.  So, we tried using a different IDE we switched from Code::Blocks to Eclipse. After,  some more work I was able to get everything to compile in Eclipse, but we wanted to fix Code::Block as it would be a lot of work to switch everything over to the new IDE. So, we tried switching our compiler from GNU GCC to Clang, but Clang was having a problem with including pthread and wouldn't work.  We also tried wiping and upgrading all of the related software including Code::Blocks to no avail. Then on the verge of just wiping and restoring Mint on the computer, I found that Code::Blocks has global linker settings and in there was the -l+ that was breaking all of our Code::Blocks projects and somehow survied the purge of Code::Blocks, after deleting it everything compiled just fine.

The plan for this next week is to focus on writing up documentation for future Gateware developers and end users.  This will be in the hopes of helping dev out of the sticky situations that exist on Mac and Linux. Such as the understanding of runloops and similar API on Mac, or the lack of resources on Linux. 

Monday, February 11, 2019

Finshing up GController

 02/11/2019

Month 4, Week 1

Author: Devin Wright

 Mac is now integrated and passes all of the unit tests, as it turns out all inputs received from the device input callbacks have unique usages(an ID of sorts) that can be used to identify the input. However, the usages are unique per controller, so I mapped both the PS4 and Xbox controller in an array of input codes and index into it using the usage and the controller id. Also, it turns out that I was receiving axis events they where simply received as misc input rather than an axis input. Integrating in the code from the demo into GController caused some issues as most of Mac's implementation was done in a separate .mm file, however, most of this was solved by moving the shared function to an .hpp, and making a wrapper class to connect back to the main GeneralController class.

This week will be spent doing some final testing for GController, as well as creating demo applications for Mac and Linux for the postmortem. GController should be ready for what will likely be my final major release for it by the end of the week. For the demos there is an old demo application on the repo, however, it appears that it was designed to work solely on Windows. Which mean I will end up having to rip out a lot of incompatible code in order to get it to work. Before I do that though I will have to look further into it to see if will be worth the time as the demo appears to be heavily direct-x.

Monday, January 28, 2019

Returning to Mac OsX

 01/23/2019

Month 3, Week 3

Author: Devin Wright

 At this time, I have finally gotten the  IOHIDManager to work so that I can detect connected controllers and receive input. The big piece I was missing to get it working was that I had to launch a separate thread that had to both create and init the IOHIDManagerRef, and then call CFRunLoopRun() which blocks the current thread and uses it to process the messages for the IOHIDManager.  I also did all of this within a class which seemed to be a necessary step as I had another demo beforehand that was implemented in a similar fashion but did not use classes, however, I am not 100% certain of this.  The working demo was also created as a command line tool (Macs version of a console app) rather than a Cocoa App which may have had an impact as well but this is untested. 

Now that I can detect the controllers I have to find out how to properly read input events from the controllers.  As of right now, I know how to register to receive the events from the controllers, however, I do not have a way to tell what button is sending the event. Also, currently I am not receiving axis events from the PS4 controller.  So, I am looking to see if I can get the information from the IOHIDElement received in the input event function. Otherwise, I may have to look into reading the output reports from the controller, the problem with that is they will most likely be unique for both the PS4 and Xbox controllers making a general implementation more difficult. 

Wednesday, January 23, 2019

Finishing the Linux implementation for GController

 01/23/2019

Month 3, Week 2

Author: Devin Wright

 This last week was spent implementing GController on Linux. I ended up running into a few roadblocks while adapting the code from my Linux demos. First, the js files that were planned to be used to read input from the controllers change formats between the different Linux distributions. This wasn't too much of a problem as I had already looked into using evdev to read from event files instead. The reason I planned to go with the js files was that I originally thought that the PS4 controller did not create a usable event file, however, with further testing, I proved that I was wrong. The other major roadblock I ran into was that evdev didn't have an obvious way to check if an event file belonged to a controller. I ended up looking through the libevdev wrapper library for an example of how to do this.  It turns out one of the ioctl request (EVOCGBIT) in the linux/input.h is caple of returning an array of long bits that holds what type of events the device is capable of.

With the Linux implementation being almost completely finished, these next two weeks will be spent on the Mac implementation. Mac will definitely be the hardest to implement as I still do not have a working demo to start from.  Currently, I am looking through the apple demos to see why their work and my do not.

Friday, January 11, 2019

Begining the Linux implementaion of Gateware

 01/11/2019

Month 3, Week1

Author: Devin Wright

This week was spent writing out the implementation for handling Xbox controller vibration on Windows, removing 32-bit from support from our cmake files, and starting on the Linux implementation of GController.  I had previously added the interface for the vibration support before the break, then I spent the first two days back writing out its unit test, implementing the Xbox version of the methods, and tested it to make sure everything works. After, that I began removing 32-bit support from the cmake files. This had to be done since as of the Mojave update for Mac Os 32-bit is no longer supported, and the cmake was unable to create a solution for Gateware on Mac.  This was done by removing the calls to create 32-bit files from the "command line" files (.bat, .command, and a unix executable for Windows, Mac, and Linux respectively).

When it comes to implementing Linux there is a lot less work compared to implementing Windows since most of the groundwork has now been laid. For Linux, I mainly just need to integrate the code from my Linux demos, and then just make whatever changes are needed to get them properly running.  The idea is to have a thread for each controller being processed and have these threads handled by the loop running the inotify events. The controllers will then be handled in their own event loops where their input will be processed and stored in the controllers' array.