r.pannkuk@gmail.com
Riley Pannkuk
  • Posts
  • Projects
    • Rumble TV >
      • Website
    • Dischord >
      • Website
    • Ping >
      • Website
    • Temple of the Water God >
      • Website
    • E.A.T: Escape Alien Terrestrials
  • Code Samples
    • [JS] Google Form Email Response
    • [JS] Google Form Trello Integration
    • [JS] Trello Card Organizer
    • [C++] Metrics Integration
    • [Python] Zero Metrics Integration
  • About Me
  • Résumé

[C++] Metrics Integration

Repository

https://bitbucket.org/r-pannkuk/c-gameanalytics/src

Summary

In modern development of games—regardless of console, PC, or mobile development—metrics have become an important tool for understanding and designing around player behavior.  Although many resources exist to help integrate a metrics system into one's own product, many of these are designed for professional and commercial use, meaning a professional license which costs several thousands of dollars, or a contractual obligation.  Neither of these is ideal for a student trying to learn the fundamentals of game programming and game design, so I looked for a more accessible service.

This led me to GameAnalytics.com, a site designed with the most minimal aspects of metrics but have some fundamental features which make it more desirable over other free alternatives:

  • No price
  • Access to all data via HTTP POST requests using their REST API
  • Customization and dashboard configuration using their web tool
  • [DEPRECATED] Support for heatmaps which allow mapping in-game of metrics you care about.

Thanks to Jason Ericson who also developed around this service, and drove a lot of my design decisions when creating this for my teams at DigiPen.

Purpose

The below code constructs an interface for interacting with the GameAnalytics.com servers, as they do not have support for C++ on their website.  The classes construct and store the different types of events that are supported by the GameAnalytics servers, and then converts them to JSON using the rapidjson library, and dispatches the HTTP POST request using cURL.  The dispatching can be done separately from the creation of events, to either send in bulk or send when a connection is guaranteed by the client.

[DEPRECATED]
Heatmaps are stored as a deque of pairs, which stores both the point (vec3) of the data given back by the server, and the value associated with the type of event.  The interface makes a request to the server to get all types from a specified period for an event, and then stores them into the deque by using rapidjson’s parser. This deque can then be used to create and maintain heatmaps by using a primitive shape and an alpha layer.

Dependencies

In order to integrate with GameAnalytics' REST API, I needed to use the following libraries / functions:
  • cURL
  • rapidjson
  • SHA-1 Hash
  • MD5 Hash

Analytics.h

/* 
--------------------------------------------------------------------------------
Author : Riley Pannkuk
Editors: N/A
--------------------------------------------------------------------------------
*/

// INTERFACE INCLUDES ----------------------------------------------------------
#include <string>
#include <deque>
#include <utility>
#include <exception>
#include <map>
#include <cfloat>
#include "Dependencies\rapidjson\document.h"
//------------------------------------------------------------------------------

#ifndef ANALYTICS_H
#define ANALYTICS_H

#define LINK_STATIC 1

#if LINK_STATIC == 0
#ifdef METRICSDLL_EXPORTS
#define METRICSDLL_API __declspec(dllexport)
#else
#define METRICSDLL_API __declspec(dllimport)
#endif
#else
#define METRICSDLL_API
#endif



namespace analytics
{
  // DEFINITIONS /////////////////////////////////////////////////////////////////

-

namespace category { std::string const design = "design"; std::string const error = "error"; std::string const business = "business"; std::string const user = "user"; }; namespace severity { std::string const critical = "critical"; std::string const error = "error"; std::string const warning = "warning"; std::string const info = "info"; std::string const debug = "debug"; }; std::string const game_key = "FILL_ME_IN"; /* Key for Game */ std::string const secret_key = "FILL_ME_IN"; /* Secret Key for (used for validation) */ std::string const data_api_key = "FILL_ME_IN"; /* Unused currently */ struct vec3 { vec3(void) : x(0.f), y(0.f), z(0.f) {}; vec3(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {}; bool operator!=(const vec3& rhs) const { return ((x!=rhs.x)&&(y!=rhs.y)&&(z!=rhs.z)) ? true : false; }; bool operator==(const vec3& rhs) const { return ((x==rhs.x)&&(y==rhs.y)&&(z==rhs.z)) ? true : false; }; float x; float y; float z; }; const vec3 NULL_VEC(FLT_MAX,FLT_MAX,FLT_MAX); const std::deque<std::pair<vec3, float>> NULL_HEATMAP; const std::string NULL_TIME_START = "00000000"; const std::string NULL_TIME_END = "99999999"; int _data_to_string(char *data, size_t size, size_t nmemb, std::string *result); //////////////////////////////////////////////////////////////////////////////// // EXCEPTIONS //////////////////////////////////////////////////////////////////

-

class analyticsException : public std::exception { public: virtual const char* what() const throw() = 0; }; class excInvalidKeys : public analyticsException { public: virtual const char* what() const throw() { return "ANALYTICS EXCEPTION: Invalid keys. Please check your project page to see if your keys are correct and try again."; } }; class excFileNotFound : public analyticsException { public: virtual const char* what() const throw() { return "ANALYTICS EXCEPTION: File not found. Please check that keys.ini exists in your project."; } }; class excHeatmapLoadError : public analyticsException { public: METRICSDLL_API excHeatmapLoadError(std::string a, std::string e) : area(a), eventID(e) {}; virtual const char* what() const throw() { return std::string("ANALYTICS EXCEPTION: Unable to obtain heatmap data from source <" + area + "><" + eventID + ">.").c_str(); } private: std::string area; std::string eventID; }; //////////////////////////////////////////////////////////////////////////////// // CLASSES ///////////////////////////////////////////////////////////////////// struct analytics_event; /** The analytics manager will be the structure that dispatches events across * the server to the website. */ class analytics_manager {

-

public: // CONSTRUCTORS METRICSDLL_API analytics_manager(std::string version); // DESTRUCTORS METRICSDLL_API ~analytics_manager(void); // METHODS void METRICSDLL_API add_event(analytics_event& event); void METRICSDLL_API dispatch_event(analytics_event& event); void METRICSDLL_API dispatch_category(std::string category); void METRICSDLL_API dispatch_all_categories(void); std::deque<std::pair<vec3, float> > METRICSDLL_API load_heatmap(std::string area, std::string event_id, std::string start_time = NULL_TIME_START, std::string end_time = NULL_TIME_END); bool METRICSDLL_API get_heatmap(std::string area, std::string eventID, std::deque<std::pair<vec3, float> > &points); private: void _set_user_id(void); void _set_session_id(void); void _dispatch_events_array(rapidjson::Value *eventsArray, std::string cat); void _write(rapidjson::Value &eventValue); void _read(std::string cat); void _read_keys(void); // MEMBERS std::string _user_id; std::string _session_id; std::string _build; rapidjson::Document *_doc; std::map<std::string, rapidjson::Value*> _json_writers; std::map<std::string, std::map< std::string, std::deque<std::pair<vec3, float> > > > _heatmaps; }; /** An event sent out to Game Analytics caching the data relating to the type * of event you want. This is the generic event class from which all other * analytics event types are derived. */ struct analytics_event {

-

//For event type reading friend class analytics_manager; // CONSTRUCTORS analytics_event(std::string _event_id, std::string _area = "NULL", vec3 _location = NULL_VEC, std::string event_type = "NULL") : event_id(_event_id), area(_area), location(_location), _event_type(event_type) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const =0; // MEMBERS const std::string event_id; std::string area; vec3 location; protected: std::string _event_type; }; // analytics_event /** A design event is an analytis_event that contains data related to gameplay * specifically. Store a value associated with the event_id, e.g. ammo * for a SHOTGUN_SHELL_PICKUP event. */ struct design_event : public analytics_event {

-

METRICSDLL_API design_event(std::string _event_id, std::string _area = "NULL", vec3 _location = NULL_VEC, float _value = unsigned(-1)) : analytics_event(_event_id, _area, _location, category::design), value(_value) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS float value; // Value associated with the event ID }; /** An error event is an analytis_event from a detected error or exception * thrown during gameplay. It supports the area and location fields from * its base analytics_event class, but NOT event_id. */ struct error_event : public analytics_event {

-

METRICSDLL_API error_event(std::string _message, std::string _severity, std::string _area = "NULL", vec3 _location = NULL_VEC ) : analytics_event("ERROR", _area, _location, category::error), message(_message), severity(_severity) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS std::string message; std::string severity; }; /** A business event is an analytis_event for analyzing business * transactions and monetization in the game. It utilizes all * fields from analytics_event, as well as it's own currency * and amount events. */ struct business_event : public analytics_event {

-

METRICSDLL_API business_event(std::string _event_id, std::string _currency = "NULL", int _amount = -1, std::string _area = "NULL", vec3 _location = NULL_VEC ): analytics_event(_event_id, _area, _location, category::business), currency(currency), amount(amount) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS std::string currency; //Type of currency being used, e.g. "USD" int amount; //Amount the purchase cost }; /** A user event is an analytis event that provides data on the individual * playing the game. It does not make use of the event_id, area, or location * values found in the above analytics_event class. Instead, it has it's own * data fields related to the specific machine the user is on. */ struct user_event : public analytics_event {

-

METRICSDLL_API user_event(std::string _gender = "NULL", int _birth_year = -1, int _friend_count = -1, std::string _facebook_id = "NULL", std::string _googleplus_id = "NULL", std::string _ios_id = "NULL", std::string _android_id = "NULL", std::string _adtruth_id = "NULL", std::string _platform = "NULL", std::string _device = "NULL", std::string _os_major = "NULL", std::string _os_minor = "NULL", std::string _install_publisher = "NULL", std::string _install_site = "NULL", std::string _install_campaign = "NULL", std::string _install_adgroup = "NULL", std::string _install_ad = "NULL", std::string _install_keyword = "NULL") : analytics_event("NULL", "NULL", vec3(), category::user), gender(_gender), birth_year(_birth_year), friend_count(_friend_count), facebook_id(_facebook_id), googleplus_id(_googleplus_id), ios_id(_ios_id), android_id(_android_id), adtruth_id(_adtruth_id), platform(_platform), device(_device), os_major(_os_major), os_minor(_os_minor), install_publisher(_install_publisher), install_site(_install_site), install_campaign(_install_campaign), install_adgroup(_install_adgroup), install_ad(_install_ad), install_keyword(_install_keyword) {}; virtual void _write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const; // MEMBERS std::string gender; int birth_year; int friend_count; std::string facebook_id; std::string googleplus_id; std::string ios_id; std::string android_id; std::string adtruth_id; std::string platform; std::string device; std::string os_major; std::string os_minor; std::string install_publisher; std::string install_site; std::string install_campaign; std::string install_adgroup; std::string install_ad; std::string install_keyword; }; //////////////////////////////////////////////////////////////////////////////// }; #endif // ANALYTICS_H

Analytics.cpp

/* 
--------------------------------------------------------------------------------
Author : Riley Pannkuk
Editors: N/A
--------------------------------------------------------------------------------
*/

// IMPLEMENTATION INCLUDES /////////////////////////////////////////////////////
// PCH

// this
#include "analytics.h"

// Dependencies
extern "C"
{
#ifdef _WIN32
  #include <windows.h>
  #include <rpc.h>
  #pragma comment(lib, "rpcrt4.lib")

#else
  #include <uuid/uuid.h>
  #include <sys/ioctl.h>
  #include <linux/hdreg.h>
  #include <ifstream>
  #include <sstream>

#endif
}

#include "Dependencies\curl\curl.h"
#include "Dependencies\sha1\sha1.h"
#include "Dependencies\md5\md5.h"
#include "Dependencies\rapidjson\stringbuffer.h"
#include "Dependencies\rapidjson\writer.h"
#include "Dependencies\rapidjson\stringbuffer.h"
////////////////////////////////////////////////////////////////////////////////

namespace analytics
{

//  URL STRING  //////////////////////////////////////////////////////////////////////

static std::string endpoint_url = "http://api.gameanalytics.com/1/";          /* http://api.gameanalytics.com/API_VERSION/GAME_KEY/CATEGORY:80 */

////////////////////////////////////////////////////////////////////////////////


/**
 *  \brief A helper function for obtaining a globally unique id.
 *
 *  Generates a globally unique ID for use in creating a unique session
 *  number.  This function is dependent on OS and supports both WIN32 
 *  and UNIX. 
 *
 *  \returns The unique string ID.
 */
std::string get_guid_string(void)
{

-

//WIN Only #ifdef WIN32 //// Obtain globally unique identifier GUID id; CoCreateGuid(&id); //Store the unique identifer as an ASCII string RPC_CSTR str; UuidToString((UUID*)&id, &str); //Convert to std::string return LPSTR(str); //LINUX / MAC #else //Obtain globally unique identifier uuid_t id; uuid_generate(id); //Store the unique identifier as an ASCII string char[16] str; uuid_unparse(it, &str); //Convert to std::string return std::string(str); #endif } /** * \brief Constructor for an analytics system manager. * * Generates an analytics system manager by setting up the proper * CURL initializer and then a series of JSON arrays using rapidjson. * It is in the constructor that the user sets the Build, User, and * Session information that all events will use when sent. * * \param str The build version, as a std::string. * * \throws If attempting to read from a file, will throw a bad read * exception. */ analytics_manager::analytics_manager(std::string str) : _build(str) {

-

//Initialize libcurl curl_global_init(CURL_GLOBAL_ALL); if(game_key == "FILL_ME_IN" || secret_key == "FILL_ME_IN" || data_api_key == "FILL_ME_IN") throw excInvalidKeys(); _doc = new rapidjson::Document; _doc->SetObject(); _json_writers[category::design] = new rapidjson::Value(rapidjson::kArrayType); _json_writers[category::error] = new rapidjson::Value(rapidjson::kArrayType); _json_writers[category::business] = new rapidjson::Value(rapidjson::kArrayType); _json_writers[category::user] = new rapidjson::Value(rapidjson::kArrayType); _set_user_id(); _set_session_id(); } /** * \brief Destructor for an analytics system manager. * * Deletes the created rapidjson::Arrays and deletes the used * rapidjson::Doc. */ analytics_manager::~analytics_manager(void) {

-

delete _json_writers[category::design]; delete _json_writers[category::error]; delete _json_writers[category::business]; delete _json_writers[category::user]; delete _doc; } /** * \brief Obtains a user id from the user by reading the serial number * from the user's hard drive. * * This function is OS specific, and will use either the * GetVolumeInformation function from windows.h or the user's * /dev/hda file in order to read the serial number of the machine's * hard drive. */ void analytics_manager::_set_user_id(void) {

-

//WIN Only #ifdef WIN32 //Obtain a unique id from the serial number of the hard drive DWORD serialNumber; if (GetVolumeInformation( NULL, NULL, MAX_PATH+1, &serialNumber, NULL, NULL, NULL, MAX_PATH+1)) { _user_id = std::to_string(serialNumber); } //Linux / OSX #else //Obtain a unique id from the serial number of the hard drive static struct hd_driveid hd; int fd; fd = open("/dev/hda", O_RDONLY | O_NONBLOCK); _user_id = hd.serial_no; #endif } /** * \brief Creates and stores a globally unique session id. * * Uses the get_guid_string function from above. */ void analytics_manager::_set_session_id(void) {

-

_session_id = get_guid_string(); } /** * \brief JSON write function for an analytics system. * * When sending an event, the user needs to send a combined * JSON array containing global information (user id, session id, * and build), as well as the data pertaining to the event. This * function writes the global data to a rapidjson::Value. * * \param eventValues The rapidjson::Value object to write values * to. */ void analytics_manager::_write(rapidjson::Value &eventValues) {

-

eventValues.AddMember("user_id", rapidjson::StringRef(_user_id.c_str()), _doc->GetAllocator()); eventValues.AddMember("session_id", rapidjson::StringRef(_session_id.c_str()), _doc->GetAllocator()); eventValues.AddMember("build", rapidjson::StringRef(_build.c_str()), _doc->GetAllocator()); } /** * \brief Adds an event to the backlog of analytics events. * * Depending on the category of the event (design, error, etc.), * the event will be appended to the rapidjson::Array for the * specified category, to be sent later. THIS FUNCTION DOES NOT * SEND EVENTS. THEY MUST BE SENT MANUALLY. * * \param e The analytics event to send out. */ void analytics_manager::add_event(analytics_event &e) {

-

// Create a rapidjson::Value which will be appended to the proper array. rapidjson::Value eventValue; eventValue.SetObject(); // Write the global and event-specific parameters to the Value. _write(eventValue); e._write(eventValue, _doc->GetAllocator()); // Append the Value onto the category's Array. _json_writers[e._event_type]->PushBack(eventValue, _doc->GetAllocator()); } /** * \brief Dispatches a single event in one go to the analytics server. * * This fire-and-forget function sends an event immediately without * appending to the category array. This is useful in the case that * you would need to send an event in one small moment, where it would * be unwise or slow otherwise to send an entire category of events. * An example would be the Game:Start event, which should be sent * immediately in order to track the session length of the game. * * \param e The analytics event to send out. */ void analytics_manager::dispatch_event(analytics_event& e) {

-

// Create a rapidjson::Value and rapidjson::Array to dispatch. rapidjson::Value eventValue, eventArray(rapidjson::kArrayType); eventValue.SetObject(); // Write the global and event-specific values to the Value. _write(eventValue); e._write(eventValue, _doc->GetAllocator()); // Push the Value onto the Array. eventArray.PushBack(eventValue, _doc->GetAllocator()); // Dispatch out the array to the server. _dispatch_events_array(&eventArray, e._event_type); } /** * \brief Dispatches a single category of events to the server. * * Takes a specific rapidjson::Array based on category (design, * error, business, etc.) and dispatches the events out to the * Analytics server. This is useful for when only one category * should be sent, as to reduce time spent trying to reach the * server. * * \param category The specific category to dispatch. */ void analytics_manager::dispatch_category(std::string category) {

-

_dispatch_events_array(_json_writers[category], category); } /** * \brief Dispatches all categories of events to the server. * * Takes all specific rapidjson::Arrays and dispatches the * events out to the Analytics server. This is useful for when * it is appropriate to send all events to the server, like * when the player requests the game to quit. */ void analytics_manager::dispatch_all_categories(void) {

-

_dispatch_events_array(_json_writers[category::design], category::design); _dispatch_events_array(_json_writers[category::error], category::error); _dispatch_events_array(_json_writers[category::business], category::business); _dispatch_events_array(_json_writers[category::user], category::user); } /** * \brief Takes a rapidjson::Array and dispatches all it's events * to the Analytics server. * * Closes off the referenced rapidjson::Array and then creates a * string buffer to convert the JSON to a string. It then creates * a POST request using CURL with the provided keys for your game, * dispatching the POST request and printing out the error state * into the console. The function will clear the referenced array. * * \param category The specific category to dispatch. */ void analytics_manager::_dispatch_events_array(rapidjson::Value* eventsArray, std::string category) {

-

// Close off the array eventsArray->End(); // Get data from JSON Writer rapidjson::StringBuffer strbuff; rapidjson::Writer<rapidjson::StringBuffer> writer(strbuff); eventsArray->Accept(writer); std::string data = strbuff.GetString(); // Empty data check if(data == "[]") return; CURL * curl = curl_easy_init(); if(curl) { // Request URL std::string url = endpoint_url + game_key + "/" + category; // Authorization std::string authorization = "Authorization:" + md5(data + secret_key); // Authorization header struct struct curl_slist *chunk = NULL; chunk = curl_slist_append(chunk, authorization.c_str()); // Set URL curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // Set the POST data (string of events) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); // Set the headers (hashed secret key and the data) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); // Set request mode to POST curl_easy_setopt(curl, CURLOPT_POST, 1); // Request CURLcode res = curl_easy_perform(curl); // Check the error flag if(res != CURLE_OK) { std::cout << "Curl failed to process analytics [" << category << "]. "; std::cout << curl_easy_strerror(res) << std::endl; } // Free data curl_slist_free_all(chunk); // Curl cleanup curl_easy_cleanup(curl); } // Clear the array. _json_writers[category]->Clear(); } /** * \brief Loads heatmap data for a specific event and area. * * Takes the given area and given event and requests the server * to send all captured events back tot he user. The user can * also supply two date arguments (start, end), which will be * used to constrain the events to a certain range. * * \param area The area the category is in. This is usually the level. * * \param event_id The event itself to poll for. * * \param start_time Optional. Constraint for events before a certain range. * * \param end_time Optional. Constraint for events after a certain range. * * \returns The heatmap requested in a std::deque form. */ std::deque<std::pair<vec3, float> >analytics_manager::load_heatmap(std::string area, std::string event_id, std::string start_time, std::string end_time) {

-

CURL * curl = curl_easy_init(); if(curl) { // Request URL for heatmap data std::string front_url = "http://data-api.gameanalytics.com/heatmap/?"; std::string request = "game_key=" + game_key + "&event_ids=" + event_id + "&area=" + area; std::string date_request = ""; // Check if user supplied date range if(start_time != NULL_TIME_START || end_time != NULL_TIME_END) { date_request += std::string("&start_ts=") + start_time + std::string("&end_ts=") + end_time; } // Authorization std::string authorization = "Authorization:" + md5(request + data_api_key); // Completed URL std::string url = front_url + request + date_request; // Authorization header struct struct curl_slist *chunk = NULL; chunk = curl_slist_append(chunk, authorization.c_str()); std::string result; // Set URL curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // Set the headers (hashed API key and the data) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); // Set the callback to deal with the returned data curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _data_to_string); // Pass a string to be used in the callback function to collect the data curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); // Send the request CURLcode res = curl_easy_perform(curl); // Check the error flag. if(res != CURLE_OK) { std::cout << "Curl failed to load heatmap [" << event_id << "][" << area << "]. "; std::cout << curl_easy_strerror(res) << std::endl; } // Receive JSON data rapidjson::Document doc; doc.SetObject(); std::deque<std::pair<vec3, float> > points; if(doc.Parse<0>(result.c_str()).HasParseError()) { throw excHeatmapLoadError(area, event_id); return NULL_HEATMAP; } if(!doc.HasMember("x") || !doc.HasMember("y") || !doc.HasMember("z") || !doc.HasMember("value")) { throw excHeatmapLoadError(area, event_id); return NULL_HEATMAP; } rapidjson::Value & x = doc["x"]; rapidjson::Value & y = doc["y"]; rapidjson::Value & z = doc["z"]; rapidjson::Value & value = doc["value"]; for(unsigned int i = 0; i < x.Size(); ++i) { // Construct a new point from the data vec3 location(x[i].GetDouble(), y[i].GetDouble(), z[i].GetDouble()); // Store in new points deque // Explicit conversion to float (GameAnalytics service only stores values as floats, and rapidjson will store doubles) points.push_back(std::pair<vec3, float>(location, float(value[i].GetDouble()))); } // Insert into heatmaps structure organized by area. auto area_heatmaps = _heatmaps.find(area); auto heatmap = std::pair<std::string, std::deque<std::pair<vec3, float> > >(area, points); // Check if heatmap already exists for this area if(area_heatmaps == _heatmaps.end()) { // If not, create one std::map< std::string, std::deque<std::pair<vec3, float> > > newHeatmapSet; _heatmaps.insert(std::pair<std::string, std::map< std::string, std::deque<std::pair<vec3, float> > > >(area, newHeatmapSet)); } // Otherwise just insert into the existing one. else { area_heatmaps->second.insert(heatmap); } // Curl cleanup. curl_easy_cleanup(curl); curl_slist_free_all(chunk); return points; } return NULL_HEATMAP; } /** * \brief JSON write function for a design event. * * This is the second half of writing data to a rapidjson::Value. * Requires a valid rapidjson::Value to store parameters into. * In all instances where strings are parameters, creates a copy * using allocator. * * \param eventValues The rapidjson::Value object to write values * to. * * \param allocator The allocator used in generating copies of data * (strings). */ void design_event::_write(rapidjson::Value &eventValues, rapidjson::MemoryPoolAllocator<> &allocator) const {

-

// Add the event id rapidjson::Value v_event_id(event_id.c_str(), allocator); eventValues.AddMember("event_id", v_event_id, allocator); // Add the value (if applicable) if(value != unsigned(-1)) { eventValues.AddMember("value", value, allocator); } // Add the area (if stated) if(area != "NULL") { rapidjson::Value v_area(area.c_str(), allocator); eventValues.AddMember("area", v_area, allocator); } // Add the location (if stated) if(location != NULL_VEC) { eventValues.AddMember("x", location.x, allocator); eventValues.AddMember("y", location.y, allocator); eventValues.AddMember("z", location.z, allocator); } } /** * \brief JSON write function for an error event. * * This is the second half of writing data to a rapidjson::Value. * Requires a valid rapidjson::Value to store parameters into. * In all instances where strings are parameters, creates a copy * using allocator. * * \param eventValues The rapidjson::Value object to write values * to. * * \param allocator The allocator used in generating copies of data * (strings). */ void error_event::_write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const {

-

// Convert the error event's message and severity to strings. rapidjson::Value v_message(message.c_str(), allocator); rapidjson::Value v_severity(severity.c_str(), allocator); eventArray.AddMember("message", v_message, allocator); eventArray.AddMember("severity", v_severity, allocator); // Add area if it was stated. if(area != "NULL") { rapidjson::Value v_area(area.c_str(), allocator); eventArray.AddMember("area", v_area, allocator); } // Add location if it was stated. if(location != NULL_VEC) { eventArray.AddMember("x", location.x, allocator); eventArray.AddMember("y", location.y, allocator); eventArray.AddMember("z", location.z, allocator); } } /** * \brief JSON write function for a business event. * * This is the second half of writing data to a rapidjson::Value. * Requires a valid rapidjson::Value to store parameters into. * In all instances where strings are parameters, creates a copy * using allocator. * * \param eventValues The rapidjson::Value object to write values * to. * * \param allocator The allocator used in generating copies of data * (strings). */ void business_event::_write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const {

-

// Convert event id and add. rapidjson::Value v_event_id(event_id.c_str(), allocator); eventArray.AddMember("event_id", v_event_id, allocator); // If a currencey was used, add it. if(currency != "NULL") { eventArray.AddMember("currency", rapidjson::StringRef(currency.c_str()), allocator); } // If an amount was used, add it. if(amount != -1) { eventArray.AddMember("amount", amount, allocator); } // If area was stated, add it. if(area != "NULL") { eventArray.AddMember("area", rapidjson::StringRef(area.c_str()), allocator); } // If location was stated, add it. if(location != NULL_VEC) { eventArray.AddMember("x", location.x, allocator); eventArray.AddMember("y", location.y, allocator); eventArray.AddMember("z", location.z, allocator); } } /** * \brief JSON write function for a user event. * * This is the second half of writing data to a rapidjson::Value. * Requires a valid rapidjson::Value to store parameters into. * In all instances where strings are parameters, creates a copy * using allocator. * * \param eventValues The rapidjson::Value object to write values * to. * * \param allocator The allocator used in generating copies of data * (strings). */ void user_event::_write(rapidjson::Value &eventArray, rapidjson::MemoryPoolAllocator<> &allocator) const {

-

// All of these parameters are unique to the user event. // The user event is predominantly used in mobile, and has little use // for student game teams. if(gender != "NULL") { rapidjson::Value v_gender(gender.c_str(), allocator); eventArray.AddMember("gender", v_gender, allocator); } if(birth_year != -1) { eventArray.AddMember("birth_year", birth_year, allocator); } if(friend_count != -1) { eventArray.AddMember("friend_count", friend_count, allocator); } if(platform != "NULL") { rapidjson::Value v_platform(platform.c_str(), allocator); eventArray.AddMember("platform", v_platform, allocator); } if(device != "NULL") { rapidjson::Value v_device(device.c_str(), allocator); eventArray.AddMember("device", v_device, allocator); } if(os_major != "NULL") { rapidjson::Value v_os_major(os_major.c_str(), allocator); eventArray.AddMember("os_major", v_os_major, allocator); } if(os_minor != "NULL") { rapidjson::Value v_os_minor(os_minor.c_str(), allocator); eventArray.AddMember("os_minor", v_os_minor, allocator); } if(install_publisher != "NULL") { rapidjson::Value v_install_publisher(install_publisher.c_str(), allocator); eventArray.AddMember("install_publisher", v_install_publisher, allocator); } if(install_site != "NULL") { rapidjson::Value v_install_site(install_site.c_str(), allocator); eventArray.AddMember("install_site", v_install_site, allocator); } if(install_campaign != "NULL") { rapidjson::Value v_install_campaign(install_campaign.c_str(), allocator); eventArray.AddMember("install_campaign", v_install_campaign, allocator); } if(install_ad != "NULL") { rapidjson::Value v_install_ad(install_ad.c_str(), allocator); eventArray.AddMember("install_ad", v_install_ad, allocator); } } /** * \brief Obtains a heatmap from the map of heatmaps. Token is area. * * Obtains the heatmap out of the map by using the area specified to * search for the heatmap. This function should only be called after * obtaining a heatmap via load_heatmap. * * \param area The area to search for the heatmap. * * \param locations The deque object to store the heatmap into. */ bool analytics_manager::get_heatmap(std::string area, std::string eventID, std::deque<std::pair<vec3, float> >& locations) {

-

// Get an iterator to the heatmaps object. auto area_it = _heatmaps.find(area); if (area_it == _heatmaps.end()) return false; auto event_it = area_it->second.find(eventID); if(event_it == area_it->second.end()) return false; // Obtain the heatmap deque object and return. locations = event_it->second; return true; } /** * \brief Callback function for taking returned data and converting * to a string. * * When using CURL to obtain the heatmap data, we need to convert the * data to a string to parse using rapidjson. This is the callback * function to accomplish this. * * Special thanks to Jason Ericson. */ int _data_to_string(char* data, size_t size, size_t nmemb, std::string *result) {

-

int count = 0; if(result) { // Data is in a character array. Append to the string. result->append(data, size * nmemb); count = size * nmemb; } return count; } } // namespace analytics
Powered by Create your own unique website with customizable templates.