diff options
Diffstat (limited to 'LibOVR/Src/OVR_Profile.cpp')
-rw-r--r-- | LibOVR/Src/OVR_Profile.cpp | 1013 |
1 files changed, 567 insertions, 446 deletions
diff --git a/LibOVR/Src/OVR_Profile.cpp b/LibOVR/Src/OVR_Profile.cpp index bf8906e..3be43db 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,13 +21,12 @@ otherwise accompanies this software in either electronic or hard copy form. ************************************************************************************/ #include "OVR_Profile.h" -#include "OVR_Log.h" +#include "OVR_JSON.h" #include "Kernel/OVR_Types.h" #include "Kernel/OVR_SysFile.h" #include "Kernel/OVR_Allocator.h" #include "Kernel/OVR_Array.h" -#include <json/json.h> -#include <fstream> + #ifdef OVR_OS_WIN32 #include <Shlobj.h> #else @@ -41,33 +40,8 @@ otherwise accompanies this software in either electronic or hard copy form. #endif - -#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 - +#define PROFILE_VERSION 1.0 +#define MAX_PROFILE_MAJOR_VERSION 1 namespace OVR { @@ -82,7 +56,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) @@ -93,11 +67,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"); @@ -118,21 +92,11 @@ String GetBaseOVRPath(bool create_dir) } #else - // 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"; + + passwd* pwd = getpwuid(getuid()); + const char* home = pwd->pw_dir; + path = home; + path += "/.config/Oculus"; if (create_dir) { // Create the Oculus directory if it doesn't exist @@ -165,7 +129,7 @@ String GetProfilePath(bool create_dir) ProfileManager::ProfileManager() { Changed = false; - Loaded = false; + CacheDevice = Profile_Unknown; } ProfileManager::~ProfileManager() @@ -182,423 +146,410 @@ 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(); - Loaded = false; + CacheDevice = Profile_Unknown; } -// 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() { - 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"); +void ProfileManager::LoadCache(ProfileType device) +{ + Lock::Locker lockScope(&ProfileLock); + + ClearCache(); + + String path = GetProfilePath(false); + + Ptr<JSON> root = *JSON::Load(path); + if (!root || root->GetItemCount() < 3) return; - } - 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"); + + // 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 + { return; - } - - 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; + } + + DefaultProfile = item1->Value; + + // Read the number of profiles + int profileCount = (int)item2->dValue; + JSON* profileItem = item2; + + for (int p=0; p<profileCount; p++) + { + profileItem = root->GetNextItem(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> 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; } + // Serializes the profiles to disk. void ProfileManager::SaveCache() { + String path = GetProfilePath(true); + Lock::Locker lockScope(&ProfileLock); - // Limit scope of reader - Json::Value root; - String path = GetProfilePath(false); - if (!parseJsonFile(path, root)) + + Ptr<JSON> oldroot = *JSON::Load(path); + if (oldroot) { - root = Json::Value(); + 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(); + } } + + // Create a new json root + Ptr<JSON> root = *JSON::CreateObject(); + root->AddNumberItem("Oculus Profile Version", PROFILE_VERSION); + root->AddStringItem("CurrentProfile", DefaultProfile); + root->AddNumberItem("ProfileCount", (double) ProfileCache.GetSize()); - // 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(); - } + // Generate a JSON subtree for each profile + for (unsigned int i=0; i<ProfileCache.GetSize(); i++) + { + Profile* profile = ProfileCache[i]; + + // Write the base profile information + JSON* json_profile = JSON::CreateObject(); + json_profile->Name = "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); - root[KEY_PROFILE_VERSION] = PROFILE_VERSION; - if (!DefaultProfile.IsEmpty()) { - root[KEY_CURRENT_PROFILE] = DefaultProfile.ToCStr(); - } + 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); + } + } - // Generate a JSON object of 'profile name' to 'profile data' - Json::Value & profiles = root[KEY_PROFILES]; - for (unsigned int i=0; i<ProfileCache.GetSize(); i++) { - const Profile * profile = ProfileCache[i]; - ProfileLoader::writeProfile(profiles, *profile); + // Add the completed user profile to the new root + root->AddItem("Profile", json_profile); } + // 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); + + if (index < ProfileCache.GetSize()) { - Json::StyledWriter writer; - std::string output = writer.write( root ); - SysFile f; - if (!f.Open(path, File::Open_Write | File::Open_Create | File::Open_Truncate, File::Mode_Write)) { - LogError("Unable to open %s for writing", path.ToCStr()); - return; - } - int written = f.Write((const unsigned char*)output.c_str(), - output.length()); - if (written != output.length()) { - LogError("Short write, only %d of %d bytes written", - written, (int)output.length()); - } - f.Close(); + Profile* profile = ProfileCache[index]; + OVR_strcpy(NameBuff, Profile::MaxNameLen, profile->Name); + return NameBuff; + } + else + { + return NULL; } } -// Returns the number of stored profiles for this device type -unsigned int ProfileManager::GetProfileCount() +bool ProfileManager::HasProfile(ProfileType device, const char* name) { Lock::Locker lockScope(&ProfileLock); - return ProfileCache.GetSize(); + + if (CacheDevice == Profile_Unknown) + LoadCache(device); + + for (unsigned i = 0; i< ProfileCache.GetSize(); i++) + { + if (ProfileCache[i] && OVR_strcmp(ProfileCache[i]->Name, name) == 0) + return true; + } + return false; } -bool ProfileManager::HasProfile(const char* name) + +// 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) { - return NULL != ProfileCache.at(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; + } } -// 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(const char* user) +Profile* ProfileManager::LoadProfile(ProfileType device, const char* user) { - // Maybe 'null' should be interpreted as 'return the default?' if (user == NULL) return NULL; Lock::Locker lockScope(&ProfileLock); - LoadCache(); - Profile * result = ProfileCache.at(user); - if (!result) { - return NULL; + + if (CacheDevice == Profile_Unknown) + LoadCache(device); + + for (unsigned int i=0; i<ProfileCache.GetSize(); i++) + { + if (OVR_strcmp(user, ProfileCache[i]->Name) == 0) + { // Found the requested user profile + Profile* profile = ProfileCache[i]; + return profile->Clone(); + } } - // Never give the caller memory that we ourselves are managing. - return result->Clone(); + + return NULL; } // Returns a profile with all system default values -Profile* ProfileManager::GetDefaultProfile() +Profile* ProfileManager::GetDeviceDefaultProfile(ProfileType device) { - return new Profile("default"); + const char* device_name = NULL; + return CreateProfileObject("default", device, &device_name); } // Returns the name of the profile that is marked as the current default user. -const char* ProfileManager::GetDefaultProfileName() +const char* ProfileManager::GetDefaultProfileName(ProfileType device) { Lock::Locker lockScope(&ProfileLock); - LoadCache(); + + if (CacheDevice == Profile_Unknown) + LoadCache(device); if (ProfileCache.GetSize() > 0) { - OVR_strcpy(NameBuff, 32, DefaultProfile); + OVR_strcpy(NameBuff, Profile::MaxNameLen, DefaultProfile); return NameBuff; } else @@ -607,12 +558,14 @@ const char* ProfileManager::GetDefaultProfileName() } } - // Marks a particular user as the current default user. -bool ProfileManager::SetDefaultProfileName(const char* name) { +bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name) +{ Lock::Locker lockScope(&ProfileLock); - LoadCache(); - // TODO: I should verify that the user is valid + + if (CacheDevice == Profile_Unknown) + LoadCache(device); +// TODO: I should verify that the user is valid if (ProfileCache.GetSize() > 0) { DefaultProfile = name; @@ -628,67 +581,132 @@ bool ProfileManager::SetDefaultProfileName(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) { - if (NULL == profile) { - return false; - } + Lock::Locker lockScope(&ProfileLock); if (OVR_strcmp(profile->Name, "default") == 0) return false; // don't save a default profile - Lock::Locker lockScope(&ProfileLock); - LoadCache(); + // TODO: I should also verify that this profile type matches the current cache + if (CacheDevice == Profile_Unknown) + LoadCache(profile->Type); // Look for the pre-existence of this profile - 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 + bool added = false; + for (unsigned int i=0; i<ProfileCache.GetSize(); i++) + { + int compare = OVR_strcmp(profile->Name, 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; - // Replace the previous instance with the new profile - ProfileCache[index] = profile->Clone(); - } else { - ProfileCache.PushBack(profile->Clone()); + Changed = true; } - 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); - int index = ProfileCache.IndexOf(profile->Name); - if (Map::npos == index) { - return false; + + 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; i<ProfileCache.GetSize(); i++) + { + if (OVR_strcmp(profile->Name, ProfileCache[i]->Name) == 0) + { + if (OVR_strcmp(profile->Name, DefaultProfile) == 0) + DefaultProfile.Clear(); + + ProfileCache.RemoveAt(i); + Changed = true; + return true; + } } - ProfileCache.RemoveAt(index); - return true; + + return false; } + + //----------------------------------------------------------------------------- // ***** Profile -Profile::Profile(const char* name) +Profile::Profile(ProfileType device, const char* name) { + Type = device; Gender = Gender_Unspecified; - PlayerHeight = DEFAULT_HEIGHT; - IPD = DEFAULT_IPD; - Name = name; + 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; } + // Computes the eye height from the metric head height -float Profile::GetEyeHeight() const +float Profile::GetEyeHeight() { 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) @@ -702,4 +720,107 @@ float Profile::GetEyeHeight() const 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 |