From 4ec40ed86509d5b8aaf18ac038836873bb45463c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 29 Dec 2013 01:12:14 -0800 Subject: Updating JSON profile serialization --- LibOVR/Src/OVR_Profile.cpp | 1013 +++++++++++++++++++------------------------- 1 file changed, 446 insertions(+), 567 deletions(-) (limited to 'LibOVR/Src/OVR_Profile.cpp') diff --git a/LibOVR/Src/OVR_Profile.cpp b/LibOVR/Src/OVR_Profile.cpp index 3be43db..bf8906e 100644 --- a/LibOVR/Src/OVR_Profile.cpp +++ b/LibOVR/Src/OVR_Profile.cpp @@ -5,9 +5,9 @@ Filename : OVR_Profile.cpp Content : Structs and functions for loading and storing device profile settings Created : February 14, 2013 Notes : - + Profiles are used to store per-user settings that can be transferred and used - across multiple applications. For example, player IPD can be configured once + across multiple applications. For example, player IPD can be configured once and reused for a unified experience across games. Configuration and saving of profiles can be accomplished in game via the Profile API or by the official Oculus Configuration Utility. @@ -21,12 +21,13 @@ otherwise accompanies this software in either electronic or hard copy form. ************************************************************************************/ #include "OVR_Profile.h" -#include "OVR_JSON.h" +#include "OVR_Log.h" #include "Kernel/OVR_Types.h" #include "Kernel/OVR_SysFile.h" #include "Kernel/OVR_Allocator.h" #include "Kernel/OVR_Array.h" - +#include +#include #ifdef OVR_OS_WIN32 #include #else @@ -40,8 +41,33 @@ otherwise accompanies this software in either electronic or hard copy form. #endif -#define PROFILE_VERSION 1.0 -#define MAX_PROFILE_MAJOR_VERSION 1 + +#define PROFILE_VERSION 2 +#define MAX_PROFILE_MAJOR_VERSION 2 + +// Many hard coded strings used in numerous locations have been +// repositioned here, so that there's no chance of a misspelling +// causing a problem. Not every string has been moved, but most of the +// repeated ones have. +#define KEY_PROFILE_VERSION "Oculus Profile Version" +#define KEY_CURRENT_PROFILE "CurrentProfile" +#define KEY_PROFILES "Profiles" +#define KEY_DEVICES "Devices" +#define KEY_GENDER "Gender" +#define KEY_PLAYER_HEIGHT "PlayerHeight" +#define KEY_IPD "IPD" +#define KEY_STRABISMUS_CORRECTION "StrabismusCorrection" + +#define KEY_LL "LL" +#define KEY_LR "LR" +#define KEY_RL "RL" +#define KEY_RR "RR" +#define KEY_EYECUP "EyeCup" +#define EPSILON 0.00001f +// 5'10" inch man +#define DEFAULT_HEIGHT 1.778f +#define DEFAULT_IPD 0.064f + namespace OVR { @@ -56,7 +82,7 @@ String GetBaseOVRPath(bool create_dir) TCHAR data_path[MAX_PATH]; SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path); path = String(data_path); - + path += "/Oculus"; if (create_dir) @@ -67,11 +93,11 @@ String GetBaseOVRPath(bool create_dir) DWORD attrib = GetFileAttributes(wpath); bool exists = attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY); if (!exists) - { + { CreateDirectory(wpath, NULL); } } - + #elif defined(OVR_OS_MAC) const char* home = getenv("HOME"); @@ -92,11 +118,21 @@ String GetBaseOVRPath(bool create_dir) } #else - - passwd* pwd = getpwuid(getuid()); - const char* home = pwd->pw_dir; - path = home; - path += "/.config/Oculus"; + // Updated the config folder location logic to rely on the + // XDG specification for config locations (the XDK location was being + // used anyway, but was hardcoded). This is analgous to using + // SHGetFolderPath in the windows implementation, rather than + // hardcoding %HOME%/AppData/Local + const char * config_home = getenv("XDG_CONFIG_HOME"); + if (NULL != config_home) { + path = config_home; + } else { + // only if XDG_CONFIG_HOME is unses does the specification say to + // fallback on the default of $HOME/.config + path = getenv("HOME"); + path += "/.config"; + } + path += "/Oculus"; if (create_dir) { // Create the Oculus directory if it doesn't exist @@ -129,7 +165,7 @@ String GetProfilePath(bool create_dir) ProfileManager::ProfileManager() { Changed = false; - CacheDevice = Profile_Unknown; + Loaded = false; } ProfileManager::~ProfileManager() @@ -146,410 +182,423 @@ ProfileManager* ProfileManager::Create() return new ProfileManager(); } -Profile* ProfileManager::CreateProfileObject(const char* user, - ProfileType device, - const char** device_name) -{ - Lock::Locker lockScope(&ProfileLock); - - Profile* profile = NULL; - switch (device) - { - case Profile_GenericHMD: - *device_name = NULL; - profile = new HMDProfile(Profile_GenericHMD, user); - break; - case Profile_RiftDK1: - *device_name = "RiftDK1"; - profile = new RiftDK1Profile(user); - break; - case Profile_RiftDKHD: - *device_name = "RiftDKHD"; - profile = new RiftDKHDProfile(user); - break; - case Profile_Unknown: - break; - } - - return profile; -} - - // Clear the local profile cache void ProfileManager::ClearCache() { Lock::Locker lockScope(&ProfileLock); - ProfileCache.Clear(); - CacheDevice = Profile_Unknown; + Loaded = false; } +// Profile loader is an intermediary that allows me to break down the +// serialization into smaller pieces that work directly against the +// new native JSON container type. This can't be done in the +// ProfileManager because it can't declare functions referencing the +// new type without polluting the header with knowledge of the JSON +// implementation +class ProfileLoader { +public: + static void loadHmd(HmdDevice & device, const Json::Value & node) { + if (node.isNull()) { + return; + } + if (node.isMember(KEY_LL)) { + device.LL = node.get(KEY_LL, 0).asInt(); + } + if (node.isMember(KEY_LR)) { + device.LR = node.get(KEY_LR, 0).asInt(); + } + if (node.isMember(KEY_RL)) { + device.RL = node.get(KEY_RL, 0).asInt(); + } + if (node.isMember(KEY_RR)) { + device.RR = node.get(KEY_RR, 0).asInt(); + } + } + + static void loadRift(RiftDevice & device, const Json::Value & node) { + if (node.isNull()) { + return; + } + String eyeCups = node.get(KEY_EYECUP, "A").asCString(); + char c = eyeCups.GetSize() ? eyeCups.GetCharAt(0) : 'A'; + switch (eyeCups.GetCharAt(0)) { + case 'A': + device.EyeCups = EyeCupType::EyeCup_A; + break; + case 'B': + device.EyeCups = EyeCupType::EyeCup_B; + break; + case 'C': + device.EyeCups = EyeCupType::EyeCup_C; + break; + } + loadHmd(device, node); + } + + // TODO migrate all the string constants up + static void loadV1Profile(Profile & out, const Json::Value & node) { + if (node.isNull()) { + return; + } + String gender = node.get(KEY_GENDER, "Unknown").asCString(); + if (gender == "Male") { + out.Gender = Profile::GenderType::Gender_Male; + } else if (gender == "Female") { + out.Gender = Profile::GenderType::Gender_Female; + } else { + out.Gender = Profile::GenderType::Gender_Unspecified; + } + + out.PlayerHeight = node.get(KEY_PLAYER_HEIGHT, DEFAULT_HEIGHT).asFloat(); + out.IPD = node.get(KEY_IPD, DEFAULT_IPD).asFloat(); + loadHmd(out.Generic, node.get("GenericHMD", Json::Value::null)); + loadRift(out.RiftDK1, node.get("RiftDK1", Json::Value::null)); + loadRift(out.RiftDKHD, node.get("RiftDKHD", Json::Value::null)); + } + + static void loadProfile(Profile & out, const Json::Value & node) { + if (node.isNull()) { + return; + } + loadV1Profile(out, node); + if (node.isMember(KEY_STRABISMUS_CORRECTION)) { + loadQuaternion(out.StrabismusCorrection, node[KEY_STRABISMUS_CORRECTION]); + } + } + + static void loadQuaternion(Quatf & out, const Json::Value & node) { + if (node.isNull()) { + return; + } + out.x = node.get("X", 0).asInt(); + out.y = node.get("Y", 0).asInt(); + out.z = node.get("Z", 0).asInt(); + out.w = node.get("W", 1).asInt(); + } + + static void saveQuaternion(Json::Value & out, const Quatf & q) { + out["X"] = q.x; + out["Y"] = q.y; + out["Z"] = q.z; + out["W"] = q.w; + } + + // TODO implement, and migrate the Devices.json parsing code to use this + static void loadMatrix(Matrix4f & out, const Json::Value & node) { + } + + // + // The general pattern on writing Json is to write the node only if + // it differs from the default, and to explicitly remove the node if it + // is the same as the default. This is important, because the load / save + // mechanism is designed to preserve any pre-existing content and not + // to touch any fields it's not aware of. + // + // Therefore, failing to write out a default value doesn't mean there's + // not already a non-default value there, hence the explicit removes. + // It gets a little verbose but the result is greater extensibility of the + // profile data by third parties. + static void writeHmdDevice(Json::Value & out, const HmdDevice & device) { + if (device.LL != 0) { + out[KEY_LL] = device.LL; + } else { + out.removeMember(KEY_LL); + } + if (device.LR != 0) { + out[KEY_LR] = device.LR; + } else { + out.removeMember(KEY_LR); + } + if (device.RL != 0) { + out[KEY_RL] = device.RL; + } else { + out.removeMember(KEY_RL); + } + if (device.RR != 0) { + out[KEY_RR] = device.RR; + } else { + out.removeMember(KEY_RR); + } + } + + static void writeRiftDevice(Json::Value & deviceNode, const RiftDevice & device) { + switch (device.EyeCups) + { + case EyeCup_B: deviceNode[KEY_EYECUP] = "B"; break; + case EyeCup_C: deviceNode[KEY_EYECUP] = "C"; break; + // A is the default, so no need to serialize it + case EyeCup_A: deviceNode.removeMember(KEY_EYECUP); break; + } + writeHmdDevice(deviceNode, device); + } + + // Applying the "remove nodes if they're empty" logic here is a little + // arduous, but it makes the resulting JSON cleaner + static void updateDeviceProfile(Json::Value & parent, const HmdDevice & device) { + String name; + Json::Value newChild; + switch (device.GetDeviceType()) { + case Profile_RiftDK1: + name = "RiftDK1"; + newChild = parent[name]; + writeRiftDevice(newChild, (RiftDevice&)device); + break; + + case Profile_RiftDKHD: + name = "RiftDKHD"; + newChild = parent[name]; + writeRiftDevice(newChild, (RiftDevice&)device); + break; + + case Profile_GenericHMD: + name = "GenericHMD"; + newChild = parent[name]; + writeHmdDevice(newChild, device); + break; + } + + // Don't write empty children + if (newChild.getMemberNames().size()) { + parent[name] = newChild; + } else { + parent.removeMember(name); + } + } + + static void writeProfile(Json::Value & parent, const Profile & profile) { + Json::Value & out = parent[profile.Name]; + + switch (profile.GetGender()) { + case Profile::Gender_Male: + out[KEY_GENDER] = "Male"; + break; + case Profile::Gender_Female: + out[KEY_GENDER] = "Female"; + break; + default: + out.removeMember(KEY_GENDER); + } + + // Epsilon is 10 micrometers. Smaller than a human hair + if (abs(profile.PlayerHeight - DEFAULT_HEIGHT) > EPSILON) { + out[KEY_PLAYER_HEIGHT] = profile.PlayerHeight; + } else { + out.removeMember(KEY_PLAYER_HEIGHT); + } + + if (abs(profile.IPD - DEFAULT_IPD) > EPSILON) { + out[KEY_IPD] = profile.IPD; + } else { + out.removeMember(KEY_IPD); + } + + if (profile.StrabismusCorrection.Distance(Quatf()) > EPSILON) { + saveQuaternion(out[KEY_STRABISMUS_CORRECTION], + profile.StrabismusCorrection); + } else { + out.removeMember(KEY_STRABISMUS_CORRECTION); + } + + Json::Value & devicesNode = out[KEY_DEVICES]; + updateDeviceProfile(devicesNode, profile.Generic); + updateDeviceProfile(devicesNode, profile.RiftDK1); + updateDeviceProfile(devicesNode, profile.RiftDKHD); + if (!devicesNode.getMemberNames().size()) { + out.removeMember(KEY_DEVICES); + } + } +}; + +// Use of STL IO is potentially avoidable with jsoncpp, if you wanted +// to load the file into a String instead and use the char* interface for +// parsing JSON in memory. Linking to C++ standard library is unavoidable +// though, but it might not take much work to remove iostream usage from +// the jsoncpp lib and make it use std::string only. +bool parseJsonFile(const String & file, Json::Value & root) { + Json::Reader reader; + String path = GetProfilePath(false); + std::ifstream in(path.ToCStr()); + if (!reader.parse(in, root)) { + // report to the user the failure and their locations in the document. + LogError("Failed to parse json file: %s\n %s", + file.ToCStr(), + reader.getFormattedErrorMessages().c_str()); + return false; + } + return true; + +} // Poplulates the local profile cache. This occurs on the first access of the profile // data. All profile operations are performed against the local cache until the // ProfileManager is released or goes out of scope at which time the cache is serialized // to disk. -void ProfileManager::LoadCache(ProfileType device) -{ - Lock::Locker lockScope(&ProfileLock); - - ClearCache(); - - String path = GetProfilePath(false); - - Ptr root = *JSON::Load(path); - if (!root || root->GetItemCount() < 3) +void ProfileManager::LoadCache() { + if (Loaded) { + return; + } + + Lock::Locker lockScope(&ProfileLock); + ClearCache(); + + Json::Value root; + if (!parseJsonFile(GetProfilePath(false), root)) { + LogError("Failed to parse configuration"); + return; + } + + if (!root.isMember(KEY_PROFILE_VERSION)) { + LogError("Profile JSON is malformed, missing version number"); + return; + } + + int major = root[KEY_PROFILE_VERSION].asInt(); + switch (major) { + case 1: { + if (root.size() < 3) { + LogError("Profile JSON is malformed, insufficient keys"); return; - - // First read the file type and version to make sure this is a valid file - JSON* item0 = root->GetFirstItem(); - JSON* item1 = root->GetNextItem(item0); - JSON* item2 = root->GetNextItem(item1); - - if (item0->Name == "Oculus Profile Version") - { - int major = atoi(item0->Value.ToCStr()); - if (major > MAX_PROFILE_MAJOR_VERSION) - return; // don't parse the file on unsupported major version number - } - else - { + } + DefaultProfile = root[KEY_CURRENT_PROFILE].asCString(); + const Json::Value & profileNode = root["Profile"]; + String name = profileNode["Name"].asCString(); + // The profile having to know it's own name is a symptom of the lack + // of a proper associative array class in the OVR codebase. + Profile * profile = new Profile(name); + ProfileLoader::loadV1Profile(*profile, profileNode); + ProfileCache.PushBack(profile); + } + break; + + case 2: { + if (!root.isMember(KEY_PROFILES)) { + LogError("Missing profile data"); return; - } - - DefaultProfile = item1->Value; - - // Read the number of profiles - int profileCount = (int)item2->dValue; - JSON* profileItem = item2; - - for (int p=0; pGetNextItem(profileItem); - if (profileItem == NULL) - break; - - if (profileItem->Name == "Profile") - { - // Read the required Name field - const char* profileName; - JSON* item = profileItem->GetFirstItem(); - - if (item && (item->Name == "Name")) - { - profileName = item->Value; - } - else - { - return; // invalid field - } - - const char* deviceName = 0; - bool deviceFound = false; - Ptr profile = *CreateProfileObject(profileName, device, &deviceName); - - // Read the base profile fields. - if (profile) - { - while (item = profileItem->GetNextItem(item), item) - { - if (item->Type != JSON_Object) - { - profile->ParseProperty(item->Name, item->Value); - } - else - { // Search for the matching device to get device specific fields - if (!deviceFound && deviceName && OVR_strcmp(item->Name, deviceName) == 0) - { - deviceFound = true; - - for (JSON* deviceItem = item->GetFirstItem(); deviceItem; - deviceItem = item->GetNextItem(deviceItem)) - { - profile->ParseProperty(deviceItem->Name, deviceItem->Value); - } - } - } - } - } - - // Add the new profile - ProfileCache.PushBack(profile); - } - } - - CacheDevice = device; + } + + const Json::Value & profiles = root[KEY_PROFILES]; + Json::Value::const_iterator itr; + for (itr = profiles.begin(); itr != profiles.end(); ++itr) { + String profileName = itr.memberName(); + Profile * profile = new Profile(profileName); + ProfileLoader::loadProfile(*profile, root[profileName]); + ProfileCache.PushBack(profile); + } + + if (!root.isMember(KEY_CURRENT_PROFILE)) { + LogError("Missing current profile"); + } else { + DefaultProfile = root[KEY_CURRENT_PROFILE].asCString(); + } + } + break; + + default: + LogError("Usupported profile version %d", major); + return; // don't parse the file on unsupported major version number + } + Loaded = true; } - // Serializes the profiles to disk. void ProfileManager::SaveCache() { - String path = GetProfilePath(true); - Lock::Locker lockScope(&ProfileLock); - - Ptr oldroot = *JSON::Load(path); - if (oldroot) + // Limit scope of reader + Json::Value root; + String path = GetProfilePath(false); + if (!parseJsonFile(path, root)) { - if (oldroot->GetItemCount() >= 3) - { - JSON* item0 = oldroot->GetFirstItem(); - JSON* item1 = oldroot->GetNextItem(item0); - oldroot->GetNextItem(item1); - - if (item0->Name == "Oculus Profile Version") - { - int major = atoi(item0->Value.ToCStr()); - if (major > MAX_PROFILE_MAJOR_VERSION) - oldroot.Clear(); // don't use the file on unsupported major version number - } - else - { - oldroot.Clear(); - } - } - else - { - oldroot.Clear(); - } + root = Json::Value(); } - - // Create a new json root - Ptr root = *JSON::CreateObject(); - root->AddNumberItem("Oculus Profile Version", PROFILE_VERSION); - root->AddStringItem("CurrentProfile", DefaultProfile); - root->AddNumberItem("ProfileCount", (double) ProfileCache.GetSize()); - - // Generate a JSON subtree for each profile - for (unsigned int i=0; iName = "Profile"; - json_profile->AddStringItem("Name", profile->Name); - const char* gender; - switch (profile->GetGender()) - { - case Profile::Gender_Male: gender = "Male"; break; - case Profile::Gender_Female: gender = "Female"; break; - default: gender = "Unspecified"; - } - json_profile->AddStringItem("Gender", gender); - json_profile->AddNumberItem("PlayerHeight", profile->PlayerHeight); - json_profile->AddNumberItem("IPD", profile->IPD); - - char* device_name = NULL; - // Create a device-specific subtree for the cached device - if (profile->Type == Profile_RiftDK1) - { - device_name = "RiftDK1"; - - RiftDK1Profile* rift = (RiftDK1Profile*)profile; - JSON* json_rift = JSON::CreateObject(); - json_profile->AddItem(device_name, json_rift); - - const char* eyecup = "A"; - switch (rift->EyeCups) - { - case EyeCup_A: eyecup = "A"; break; - case EyeCup_B: eyecup = "B"; break; - case EyeCup_C: eyecup = "C"; break; - } - json_rift->AddStringItem("EyeCup", eyecup); - json_rift->AddNumberItem("LL", rift->LL); - json_rift->AddNumberItem("LR", rift->LR); - json_rift->AddNumberItem("RL", rift->RL); - json_rift->AddNumberItem("RR", rift->RR); - } - else if (profile->Type == Profile_RiftDKHD) - { - device_name = "RiftDKHD"; - - RiftDKHDProfile* rift = (RiftDKHDProfile*)profile; - JSON* json_rift = JSON::CreateObject(); - json_profile->AddItem(device_name, json_rift); - - const char* eyecup = "A"; - switch (rift->EyeCups) - { - case EyeCup_A: eyecup = "A"; break; - case EyeCup_B: eyecup = "B"; break; - case EyeCup_C: eyecup = "C"; break; - } - json_rift->AddStringItem("EyeCup", eyecup); - //json_rift->AddNumberItem("LL", rift->LL); - //json_rift->AddNumberItem("LR", rift->LR); - //json_rift->AddNumberItem("RL", rift->RL); - //json_rift->AddNumberItem("RR", rift->RR); - } - - // There may be multiple devices stored per user, but only a single - // device is represented by this root. We don't want to overwrite - // the other devices so we need to examine the older root - // and merge previous devices into new json root - if (oldroot) - { - JSON* old_profile = oldroot->GetFirstItem(); - while (old_profile) - { - if (old_profile->Name == "Profile") - { - JSON* profile_name = old_profile->GetItemByName("Name"); - if (profile_name && OVR_strcmp(profile->Name, profile_name->Value) == 0) - { // Now that we found the user in the older root, add all the - // object children to the new root - except for the one for the - // current device - JSON* old_item = old_profile->GetFirstItem(); - while (old_item) - { - if (old_item->Type == JSON_Object - && (device_name == NULL || OVR_strcmp(old_item->Name, device_name) != 0)) - { - JSON* old_device = old_item; - old_item = old_profile->GetNextItem(old_item); - - // remove the node from the older root to avoid multiple reference - old_device->RemoveNode(); - // add the node pointer to the new root - json_profile->AddItem(old_device->Name, old_device); - } - else - { - old_item = old_profile->GetNextItem(old_item); - } - } - - break; - } - } - - old_profile = oldroot->GetNextItem(old_profile); - } - } - // Add the completed user profile to the new root - root->AddItem("Profile", json_profile); + // If the file is V1, we don't want to preserve it. + // Technically, we could make an effort to preserve it but explcitly + // remove the V1 tokens we don't use in V2, but that's a bunch of + // effort that will likely serve no useful purpose. Since the file + // isn't valid JSON if it has multiple profiles, I doubt anyone is + // extending it yet + if (root.isMember(KEY_PROFILE_VERSION) && + PROFILE_VERSION != root[KEY_PROFILE_VERSION].asInt()) { + root = Json::Value(); } - // Save the profile to disk - root->Save(path); -} - -// Returns the number of stored profiles for this device type -int ProfileManager::GetProfileCount(ProfileType device) -{ - Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); - - return (int)ProfileCache.GetSize(); -} - -// Returns the profile name of a specific profile in the list. The returned -// memory is locally allocated and should not be stored or deleted. Returns NULL -// if the index is invalid -const char* ProfileManager::GetProfileName(ProfileType device, unsigned int index) -{ - Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); + root[KEY_PROFILE_VERSION] = PROFILE_VERSION; + if (!DefaultProfile.IsEmpty()) { + root[KEY_CURRENT_PROFILE] = DefaultProfile.ToCStr(); + } - if (index < ProfileCache.GetSize()) - { - Profile* profile = ProfileCache[index]; - OVR_strcpy(NameBuff, Profile::MaxNameLen, profile->Name); - return NameBuff; + // Generate a JSON object of 'profile name' to 'profile data' + Json::Value & profiles = root[KEY_PROFILES]; + for (unsigned int i=0; iName, name) == 0) - return true; - } - return false; + return ProfileCache.GetSize(); } - -// Returns a specific profile object in the list. The returned memory should be -// encapsulated in a Ptr<> object or released after use. Returns NULL if the index -// is invalid -Profile* ProfileManager::LoadProfile(ProfileType device, unsigned int index) +bool ProfileManager::HasProfile(const char* name) { - Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); - - if (index < ProfileCache.GetSize()) - { - Profile* profile = ProfileCache[index]; - return profile->Clone(); - } - else - { - return NULL; - } + return NULL != ProfileCache.at(name); } -// Returns a profile object for a particular device and user name. The returned -// memory should be encapsulated in a Ptr<> object or released after use. Returns +// Returns a profile object for a particular device and user name. The returned +// memory should be encapsulated in a Ptr<> object or released after use. Returns // NULL if the profile is not found -Profile* ProfileManager::LoadProfile(ProfileType device, const char* user) +Profile* ProfileManager::LoadProfile(const char* user) { + // Maybe 'null' should be interpreted as 'return the default?' if (user == NULL) return NULL; Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); - - for (unsigned int i=0; iName) == 0) - { // Found the requested user profile - Profile* profile = ProfileCache[i]; - return profile->Clone(); - } + LoadCache(); + Profile * result = ProfileCache.at(user); + if (!result) { + return NULL; } - - return NULL; + // Never give the caller memory that we ourselves are managing. + return result->Clone(); } // Returns a profile with all system default values -Profile* ProfileManager::GetDeviceDefaultProfile(ProfileType device) +Profile* ProfileManager::GetDefaultProfile() { - const char* device_name = NULL; - return CreateProfileObject("default", device, &device_name); + return new Profile("default"); } // Returns the name of the profile that is marked as the current default user. -const char* ProfileManager::GetDefaultProfileName(ProfileType device) +const char* ProfileManager::GetDefaultProfileName() { Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); + LoadCache(); if (ProfileCache.GetSize() > 0) { - OVR_strcpy(NameBuff, Profile::MaxNameLen, DefaultProfile); + OVR_strcpy(NameBuff, 32, DefaultProfile); return NameBuff; } else @@ -558,14 +607,12 @@ const char* ProfileManager::GetDefaultProfileName(ProfileType device) } } + // Marks a particular user as the current default user. -bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name) -{ +bool ProfileManager::SetDefaultProfileName(const char* name) { Lock::Locker lockScope(&ProfileLock); - - if (CacheDevice == Profile_Unknown) - LoadCache(device); -// TODO: I should verify that the user is valid + LoadCache(); + // TODO: I should verify that the user is valid if (ProfileCache.GetSize() > 0) { DefaultProfile = name; @@ -581,132 +628,67 @@ bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name) // Saves a new or existing profile. Returns true on success or false on an // invalid or failed save. -bool ProfileManager::Save(const Profile* profile) +bool ProfileManager::Save(const Profile * profile) { - Lock::Locker lockScope(&ProfileLock); + if (NULL == profile) { + return false; + } if (OVR_strcmp(profile->Name, "default") == 0) return false; // don't save a default profile - // TODO: I should also verify that this profile type matches the current cache - if (CacheDevice == Profile_Unknown) - LoadCache(profile->Type); + Lock::Locker lockScope(&ProfileLock); + LoadCache(); // Look for the pre-existence of this profile - bool added = false; - for (unsigned int i=0; iName, ProfileCache[i]->Name); - - if (compare == 0) - { - // TODO: I should do a proper field comparison to avoid unnecessary - // overwrites and file saves - - // Replace the previous instance with the new profile - ProfileCache[i] = *profile->Clone(); - added = true; - Changed = true; - break; - } - } - - if (!added) - { - ProfileCache.PushBack(*profile->Clone()); - if (ProfileCache.GetSize() == 1) - CacheDevice = profile->Type; + int index = ProfileCache.IndexOf(profile->Name); + if (Map::npos == index) { + // TODO: I should do a proper field comparison to avoid unnecessary + // overwrites and file saves - Changed = true; + // Replace the previous instance with the new profile + ProfileCache[index] = profile->Clone(); + } else { + ProfileCache.PushBack(profile->Clone()); } - + Changed = true; return true; } // Removes an existing profile. Returns true if the profile was found and deleted // and returns false otherwise. -bool ProfileManager::Delete(const Profile* profile) +bool ProfileManager::Delete(const Profile * profile) { + if (NULL == profile) { + return false; + } Lock::Locker lockScope(&ProfileLock); - - if (OVR_strcmp(profile->Name, "default") == 0) - return false; // don't delete a default profile - - if (CacheDevice == Profile_Unknown) - LoadCache(profile->Type); - - // Look for the existence of this profile - for (unsigned int i=0; iName, ProfileCache[i]->Name) == 0) - { - if (OVR_strcmp(profile->Name, DefaultProfile) == 0) - DefaultProfile.Clear(); - - ProfileCache.RemoveAt(i); - Changed = true; - return true; - } + int index = ProfileCache.IndexOf(profile->Name); + if (Map::npos == index) { + return false; } - - return false; + ProfileCache.RemoveAt(index); + return true; } - - //----------------------------------------------------------------------------- // ***** Profile -Profile::Profile(ProfileType device, const char* name) +Profile::Profile(const char* name) { - Type = device; Gender = Gender_Unspecified; - PlayerHeight = 1.778f; // 5'10" inch man - IPD = 0.064f; - OVR_strcpy(Name, MaxNameLen, name); -} - - -bool Profile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "Name") == 0) - { - OVR_strcpy(Name, MaxNameLen, sval); - return true; - } - else if (OVR_strcmp(prop, "Gender") == 0) - { - if (OVR_strcmp(sval, "Male") == 0) - Gender = Gender_Male; - else if (OVR_strcmp(sval, "Female") == 0) - Gender = Gender_Female; - else - Gender = Gender_Unspecified; - - return true; - } - else if (OVR_strcmp(prop, "PlayerHeight") == 0) - { - PlayerHeight = (float)atof(sval); - return true; - } - else if (OVR_strcmp(prop, "IPD") == 0) - { - IPD = (float)atof(sval); - return true; - } - - return false; + PlayerHeight = DEFAULT_HEIGHT; + IPD = DEFAULT_IPD; + Name = name; } - // Computes the eye height from the metric head height -float Profile::GetEyeHeight() +float Profile::GetEyeHeight() const { const float EYE_TO_HEADTOP_RATIO = 0.44538f; const float MALE_AVG_HEAD_HEIGHT = 0.232f; const float FEMALE_AVG_HEAD_HEIGHT = 0.218f; - + // compute distance from top of skull to the eye float head_height; if (Gender == Gender_Female) @@ -720,107 +702,4 @@ float Profile::GetEyeHeight() return eye_height; } -//----------------------------------------------------------------------------- -// ***** HMDProfile - -HMDProfile::HMDProfile(ProfileType type, const char* name) : Profile(type, name) -{ - LL = 0; - LR = 0; - RL = 0; - RR = 0; -} - -bool HMDProfile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "LL") == 0) - { - LL = atoi(sval); - return true; - } - else if (OVR_strcmp(prop, "LR") == 0) - { - LR = atoi(sval); - return true; - } - else if (OVR_strcmp(prop, "RL") == 0) - { - RL = atoi(sval); - return true; - } - else if (OVR_strcmp(prop, "RR") == 0) - { - RR = atoi(sval); - return true; - } - - return Profile::ParseProperty(prop, sval); -} - -Profile* HMDProfile::Clone() const -{ - HMDProfile* profile = new HMDProfile(*this); - return profile; -} - -//----------------------------------------------------------------------------- -// ***** RiftDK1Profile - -RiftDK1Profile::RiftDK1Profile(const char* name) : HMDProfile(Profile_RiftDK1, name) -{ - EyeCups = EyeCup_A; -} - -bool RiftDK1Profile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "EyeCup") == 0) - { - switch (sval[0]) - { - case 'C': EyeCups = EyeCup_C; break; - case 'B': EyeCups = EyeCup_B; break; - default: EyeCups = EyeCup_A; break; - } - return true; - } - - return HMDProfile::ParseProperty(prop, sval); -} - -Profile* RiftDK1Profile::Clone() const -{ - RiftDK1Profile* profile = new RiftDK1Profile(*this); - return profile; -} - -//----------------------------------------------------------------------------- -// ***** RiftDKHDProfile - -RiftDKHDProfile::RiftDKHDProfile(const char* name) : HMDProfile(Profile_RiftDKHD, name) -{ - EyeCups = EyeCup_A; -} - -bool RiftDKHDProfile::ParseProperty(const char* prop, const char* sval) -{ - if (OVR_strcmp(prop, "EyeCup") == 0) - { - switch (sval[0]) - { - case 'C': EyeCups = EyeCup_C; break; - case 'B': EyeCups = EyeCup_B; break; - default: EyeCups = EyeCup_A; break; - } - return true; - } - - return HMDProfile::ParseProperty(prop, sval); -} - -Profile* RiftDKHDProfile::Clone() const -{ - RiftDKHDProfile* profile = new RiftDKHDProfile(*this); - return profile; -} - } // OVR -- cgit v1.2.3