summaryrefslogtreecommitdiffstats
path: root/LibOVR/Src
diff options
context:
space:
mode:
authorBrad Davis <[email protected]>2013-12-29 01:12:14 -0800
committerBrad Davis <[email protected]>2013-12-29 01:12:14 -0800
commit4ec40ed86509d5b8aaf18ac038836873bb45463c (patch)
tree4192974eb3dab2bac8ffdb508a3ec2936c351aaf /LibOVR/Src
parenta91c41e4b21b61c91df3c86c105a1ab08c6d77c6 (diff)
Updating JSON profile serialization
Diffstat (limited to 'LibOVR/Src')
-rw-r--r--LibOVR/Src/OVR_Linux_HMDDevice.cpp11
-rw-r--r--LibOVR/Src/OVR_Linux_HMDDevice.h12
-rw-r--r--LibOVR/Src/OVR_Profile.cpp1013
-rw-r--r--LibOVR/Src/OVR_Profile.h385
4 files changed, 705 insertions, 716 deletions
diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.cpp b/LibOVR/Src/OVR_Linux_HMDDevice.cpp
index cce37d5..52f537d 100644
--- a/LibOVR/Src/OVR_Linux_HMDDevice.cpp
+++ b/LibOVR/Src/OVR_Linux_HMDDevice.cpp
@@ -234,14 +234,13 @@ Profile* HMDDeviceCreateDesc::GetProfileAddRef() const
{
// Create device may override profile name, so get it from there is possible.
ProfileManager* profileManager = GetManagerImpl()->GetProfileManager();
- ProfileType profileType = GetProfileType();
const char * profileName = pDevice ?
((HMDDevice*)pDevice)->GetProfileName() :
- profileManager->GetDefaultProfileName(profileType);
+ profileManager->GetDefaultProfileName();
return profileName ?
- profileManager->LoadProfile(profileType, profileName) :
- profileManager->GetDeviceDefaultProfile(profileType);
+ profileManager->LoadProfile(profileName) :
+ profileManager->GetDefaultProfile();
}
@@ -341,7 +340,7 @@ bool HMDDevice::Initialize(DeviceBase* parent)
// Initialize user profile to default for device.
ProfileManager* profileManager = GetManager()->GetProfileManager();
- ProfileName = profileManager->GetDefaultProfileName(getDesc()->GetProfileType());
+ ProfileName = profileManager->GetDefaultProfileName();
return true;
}
@@ -372,7 +371,7 @@ bool HMDDevice::SetProfileName(const char* name)
ProfileName.Clear();
return 0;
}
- if (GetManager()->GetProfileManager()->HasProfile(getDesc()->GetProfileType(), name))
+ if (GetManager()->GetProfileManager()->HasProfile(name))
{
ProfileName = name;
return true;
diff --git a/LibOVR/Src/OVR_Linux_HMDDevice.h b/LibOVR/Src/OVR_Linux_HMDDevice.h
index d0585d1..2350958 100644
--- a/LibOVR/Src/OVR_Linux_HMDDevice.h
+++ b/LibOVR/Src/OVR_Linux_HMDDevice.h
@@ -84,10 +84,10 @@ public:
virtual bool GetDeviceInfo(DeviceInfo* info) const;
// Requests the currently used default profile. This profile affects the
- // settings reported by HMDInfo.
+ // settings reported by HMDInfo.
Profile* GetProfileAddRef() const;
- ProfileType GetProfileType() const
+ ProfileDeviceType GetProfileType() const
{
return (HResolution >= 1920) ? Profile_RiftDKHD : Profile_RiftDK1;
}
@@ -120,26 +120,26 @@ public:
// HMDDevice represents an Oculus HMD device unit. An instance of this class
// is typically created from the DeviceManager.
-// After HMD device is created, we its sensor data can be obtained by
+// After HMD device is created, we its sensor data can be obtained by
// first creating a Sensor object and then wrappig it in SensorFusion.
class HMDDevice : public DeviceImpl<OVR::HMDDevice>
{
public:
HMDDevice(HMDDeviceCreateDesc* createDesc);
- ~HMDDevice();
+ ~HMDDevice();
virtual bool Initialize(DeviceBase* parent);
virtual void Shutdown();
// Requests the currently used default profile. This profile affects the
- // settings reported by HMDInfo.
+ // settings reported by HMDInfo.
virtual Profile* GetProfile() const;
virtual const char* GetProfileName() const;
virtual bool SetProfileName(const char* name);
// Query associated sensor.
- virtual OVR::SensorDevice* GetSensor();
+ virtual OVR::SensorDevice* GetSensor();
protected:
HMDDeviceCreateDesc* getDesc() const { return (HMDDeviceCreateDesc*)pCreateDesc.GetPtr(); }
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 <json/json.h>
+#include <fstream>
#ifdef OVR_OS_WIN32
#include <Shlobj.h>
#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<JSON> 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; 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;
+ }
+
+ 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<JSON> 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<JSON> 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; 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);
-
- 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; i<ProfileCache.GetSize(); i++) {
+ const Profile * profile = ProfileCache[i];
+ ProfileLoader::writeProfile(profiles, *profile);
}
- else
+
{
- return NULL;
+ 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();
}
}
-bool ProfileManager::HasProfile(ProfileType device, const char* name)
+// Returns the number of stored profiles for this device type
+unsigned int ProfileManager::GetProfileCount()
{
Lock::Locker lockScope(&ProfileLock);
-
- 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;
+ 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; i<ProfileCache.GetSize(); i++)
- {
- if (OVR_strcmp(user, ProfileCache[i]->Name) == 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; 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;
+ 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; 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;
- }
+ 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
diff --git a/LibOVR/Src/OVR_Profile.h b/LibOVR/Src/OVR_Profile.h
index df25fea..2c5e4c2 100644
--- a/LibOVR/Src/OVR_Profile.h
+++ b/LibOVR/Src/OVR_Profile.h
@@ -6,7 +6,7 @@ Content : Structs and functions for loading and storing device profile set
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.
@@ -25,11 +25,12 @@ otherwise accompanies this software in either electronic or hard copy form.
#include "Kernel/OVR_String.h"
#include "Kernel/OVR_RefCount.h"
#include "Kernel/OVR_Array.h"
+#include "Kernel/OVR_Math.h"
namespace OVR {
// Defines the profile object for each device type
-enum ProfileType
+enum ProfileDeviceType
{
Profile_Unknown = 0,
Profile_GenericHMD = 10,
@@ -37,119 +38,17 @@ enum ProfileType
Profile_RiftDKHD = 12,
};
-class Profile;
-
-// -----------------------------------------------------------------------------
-// ***** ProfileManager
-
-// Profiles are interfaced through a ProfileManager object. Applications should
-// create a ProfileManager each time they intend to read or write user profile data.
-// The scope of the ProfileManager object defines when disk I/O is performed. Disk
-// reads are performed on the first profile access and disk writes are performed when
-// the ProfileManager goes out of scope. All profile interactions between these times
-// are performed in local memory and are fast. A typical profile interaction might
-// look like this:
//
-// {
-// Ptr<ProfileManager> pm = *ProfileManager::Create();
-// Ptr<Profile> profile = pm->LoadProfile(Profile_RiftDK1,
-// pm->GetDefaultProfileName(Profile_RiftDK1));
-// if (profile)
-// { // Retrieve the current profile settings
-// }
-// } // Profile will be destroyed and any disk I/O completed when going out of scope
-
-class ProfileManager : public RefCountBase<ProfileManager>
-{
-protected:
- // Synchronize ProfileManager access since it may be accessed from multiple threads,
- // as it's shared through DeviceManager.
- Lock ProfileLock;
- Array<Ptr<Profile> > ProfileCache;
- ProfileType CacheDevice;
- String DefaultProfile;
- bool Changed;
- char NameBuff[32];
-
-public:
- static ProfileManager* Create();
-
- // Static interface functions
- int GetProfileCount(ProfileType device);
- const char* GetProfileName(ProfileType device, unsigned int index);
- bool HasProfile(ProfileType device, const char* name);
- Profile* LoadProfile(ProfileType device, unsigned int index);
- Profile* LoadProfile(ProfileType device, const char* name);
- Profile* GetDeviceDefaultProfile(ProfileType device);
- const char* GetDefaultProfileName(ProfileType device);
- bool SetDefaultProfileName(ProfileType device, const char* name);
- bool Save(const Profile* profile);
- bool Delete(const Profile* profile);
-
-protected:
- ProfileManager();
- ~ProfileManager();
- void LoadCache(ProfileType device);
- void SaveCache();
- void ClearCache();
- Profile* CreateProfileObject(const char* user,
- ProfileType device,
- const char** device_name);
-};
-
-//-------------------------------------------------------------------
-// ***** Profile
-
-// The base profile for all users. This object is not created directly.
-// Instead derived device objects provide add specific device members to
-// the base profile
-
-class Profile : public RefCountBase<Profile>
-{
-public:
- enum { MaxNameLen = 32 };
-
- enum GenderType
- {
- Gender_Unspecified = 0,
- Gender_Male = 1,
- Gender_Female = 2
- };
-
- ProfileType Type; // The type of device profile
- char Name[MaxNameLen]; // The name given to this profile
-
-protected:
- GenderType Gender; // The gender of the user
- float PlayerHeight; // The height of the user in meters
- float IPD; // Distance between eyes in meters
-
-public:
- virtual Profile* Clone() const = 0;
-
- // These are properties which are intrinsic to the user and affect scene setup
- GenderType GetGender() { return Gender; };
- float GetPlayerHeight() { return PlayerHeight; };
- float GetIPD() { return IPD; };
- float GetEyeHeight();
-
- void SetGender(GenderType gender) { Gender = gender; };
- void SetPlayerHeight(float height) { PlayerHeight = height; };
- void SetIPD(float ipd) { IPD = ipd; };
-
-protected:
- Profile(ProfileType type, const char* name);
-
- virtual bool ParseProperty(const char* prop, const char* sval);
-
- friend class ProfileManager;
-};
+// HMDProfile and it's child classes, RiftProfile, RiftDk1Profile and
+// RiftDKHDProfile represent the intersection of 'per-user' and 'per-device'
+// settings.
+//
//-----------------------------------------------------------------------------
// ***** HMDProfile
// The generic HMD profile is used for properties that are common to all headsets
-class HMDProfile : public Profile
+class HmdDevice
{
protected:
// FOV extents in pixels measured by a user
@@ -159,8 +58,7 @@ protected:
int RR; // right eye outer extent
public:
- virtual Profile* Clone() const;
-
+ virtual ~HmdDevice() {}
void SetLL(int val) { LL = val; };
void SetLR(int val) { LR = val; };
void SetRL(int val) { RL = val; };
@@ -170,13 +68,19 @@ public:
int GetLR() { return LR; };
int GetRL() { return RL; };
int GetRR() { return RR; };
+ virtual ProfileDeviceType GetDeviceType() const {
+ return Profile_GenericHMD;
+ }
protected:
- HMDProfile(ProfileType type, const char* name);
-
- virtual bool ParseProperty(const char* prop, const char* sval);
-
- friend class ProfileManager;
+ HmdDevice() {
+ LL = 0;
+ LR = 0;
+ RL = 0;
+ RR = 0;
+ }
+ friend class ProfileLoader;
+ friend class Profile;
};
// For headsets that use eye cups
@@ -187,52 +91,259 @@ enum EyeCupType
EyeCup_C = 2
};
-//-----------------------------------------------------------------------------
-// ***** RiftDK1Profile
-
-// This profile is specific to the Rift Dev Kit 1 and contains overrides specific
-// to that device and lens cup settings.
-class RiftDK1Profile : public HMDProfile
-{
+class RiftDevice : public HmdDevice {
protected:
EyeCupType EyeCups; // Which eye cup does the player use
public:
- virtual Profile* Clone() const;
-
EyeCupType GetEyeCup() { return EyeCups; };
void SetEyeCup(EyeCupType cup) { EyeCups = cup; };
protected:
- RiftDK1Profile(const char* name);
+ RiftDevice() {
+ EyeCups = EyeCup_A;
+ }
+ friend class ProfileLoader;
+ friend class Profile;
+};
- virtual bool ParseProperty(const char* prop, const char* sval);
+//-----------------------------------------------------------------------------
+// ***** RiftDK1Profile
- friend class ProfileManager;
+// This profile is specific to the Rift Dev Kit 1 and contains overrides specific
+// to that device and lens cup settings.
+class RiftDK1Device : public RiftDevice
+{
+public:
+ virtual ProfileDeviceType GetDeviceType() const {
+ return Profile_RiftDK1;
+ }
+
+protected:
+ RiftDK1Device() {
+
+ }
+ friend class ProfileLoader;
+ friend class Profile;
};
//-----------------------------------------------------------------------------
// ***** RiftDKHDProfile
-// This profile is specific to the Rift HD Dev Kit and contains overrides specific
+// This profile is specific to the Rift HD Dev Kit and contains overrides specific
// to that device and lens cup settings.
-class RiftDKHDProfile : public HMDProfile
+class RiftDKHDDevice : public RiftDevice
{
+public:
+ virtual ProfileDeviceType GetDeviceType() const {
+ return Profile_RiftDKHD;
+ }
+
protected:
- EyeCupType EyeCups; // Which eye cup does the player use
+ RiftDKHDDevice() {
+ }
+ friend class ProfileLoader;
+ friend class Profile;
+};
+template <class KeyType>
+class KeyedObject {
public:
- virtual Profile* Clone() const;
+ virtual ~KeyedObject() {
+ }
+ virtual const KeyType & GetKey() const = 0;
+};
+//-------------------------------------------------------------------
+// ***** Profile
- EyeCupType GetEyeCup() { return EyeCups; };
- void SetEyeCup(EyeCupType cup) { EyeCups = cup; };
+// The base profile for all users. This object is not created directly.
+//
+class Profile : public RefCountBase<Profile>, KeyedObject<String>
+{
+public:
+ enum GenderType
+ {
+ Gender_Unspecified = 0,
+ Gender_Male = 1,
+ Gender_Female = 2
+ };
+
+ String Name; // The name given to this profile
protected:
- RiftDKHDProfile(const char* name);
+ GenderType Gender; // The gender of the user
+ float PlayerHeight; // The height of the user in meters
+ float IPD; // Distance between eyes in meters
+ Quatf StrabismusCorrection; // Amount to rotate modelview matrix to correct for corss-eyed vision
+ // Should be applied as is to the left eye, and inverted to apply to the
+ // right eye
+ HmdDevice Generic;
+ RiftDK1Device RiftDK1;
+ RiftDKHDDevice RiftDKHD;
- virtual bool ParseProperty(const char* prop, const char* sval);
+public:
+ const String & GetKey() const { return Name; };
+ // These are properties which are intrinsic to the user and affect scene setup
+ GenderType GetGender() const { return Gender; };
+ float GetPlayerHeight() const { return PlayerHeight; };
+ float GetIPD() const { return IPD; };
+ float GetEyeHeight() const;
+ const Quatf & GetStrabismusCorrection() const { return StrabismusCorrection; };
+ HmdDevice & GetGenericDevice() { return Generic; }
+ RiftDK1Device & GetRiftDK1Device() { return RiftDK1; }
+ RiftDKHDDevice & GetRiftDKHDDevice() { return RiftDKHD; }
+ const HmdDevice & GetGenericDevice() const { return Generic; }
+ const RiftDK1Device& GetRiftDK1Device() const { return RiftDK1; }
+ const RiftDKHDDevice&GetRiftDKHDDevice() const { return RiftDKHD; }
+
+ void SetGender(GenderType gender) { Gender = gender; };
+ void SetPlayerHeight(float height) { PlayerHeight = height; };
+ void SetIPD(float ipd) { IPD = ipd; };
+ void SetStrabismusCorrection(const Quatf & quat) { StrabismusCorrection = quat; };
+ Profile * Clone() const {
+ return new Profile(*this);
+ }
+protected:
+ Profile(const char* name);
friend class ProfileManager;
+ friend class ProfileLoader;
+};
+
+
+/*
+ * A really hacky low-performing associative array, because the
+ * ProfileManager is attempting to mimic the functionality of one
+ *
+ * It should have the same performance characteristics as the
+ * previous ProfileManager embedded implementation. I'm not write a
+ * full treemap or hashmap implementaiton because, seriously, there are
+ * plenty out there in the standard libraries, and if OVR doesn't want to
+ * use them then I'm not going to get roped into building one just to
+ * support the policy of never using STL.
+ *
+ * Using at(KeyType) instead of operator[KeyType] since the latter would
+ * interfere with ability to access the index based base class operator[]
+ */
+template <class ValueType, class KeyType>
+class AssociativePtrArray : public Array<Ptr<ValueType> > {
+public:
+ enum {
+ npos = -1
+ };
+
+ int IndexOf(const KeyType & key) const {
+ for (int i = 0; i< this->GetSize(); ++i) {
+ const Ptr<ValueType> & data = this->Data.Data[i];
+ if (data && data->GetKey() == key) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ ValueType * at(const KeyType & key) {
+ int index = IndexOf(key);
+ if (npos == index) {
+ return Ptr<ValueType>();
+ }
+ Ptr<ValueType> & ptr = this->Data.Data[index];
+ return ptr;
+ }
+
+ const ValueType * at(const KeyType & key) const {
+ int index = IndexOf(key);
+ if (npos == index) {
+ return Ptr<ValueType>();
+ }
+ const Ptr<ValueType> & ptr = this->Data.Data[index];
+ return ptr;
+ }
+};
+
+// -----------------------------------------------------------------------------
+// ***** ProfileManager
+
+// Profiles are interfaced through a ProfileManager object. Applications should
+// create a ProfileManager each time they intend to read or write user profile data.
+// The scope of the ProfileManager object defines when disk I/O is performed. Disk
+// reads are performed on the first profile access and disk writes are performed when
+// the ProfileManager goes out of scope. All profile interactions between these times
+// are performed in local memory and are fast. A typical profile interaction might
+// look like this:
+//
+// {
+// Ptr<ProfileManager> pm = *ProfileManager::Create();
+// Ptr<Profile> profile = pm->LoadProfile(pm->GetDefaultProfileName());
+// if (profile)
+// { // Retrieve the current profile settings
+// }
+// } // Profile will be destroyed and any disk I/O completed when going out of scope
+
+class ProfileManager : public RefCountBase<ProfileManager>
+{
+protected:
+ // Synchronize ProfileManager access since it may be accessed from multiple threads,
+ // as it's shared through DeviceManager.
+ Lock ProfileLock;
+ typedef AssociativePtrArray<Profile, String> Map;
+ Map ProfileCache;
+ String DefaultProfile;
+ bool Changed;
+ bool Loaded;
+ // A container for the name data returned by GetDefaultProfileName()
+ // which can't be a String, because once acquired by the caller,
+ // it can't be allowed to be deallocated without risk. Ultimately
+ // this is caused by the inability to be able to safely return the
+ // String type from methods, which itself boils down to the problem
+ // with providing an explicit operator const char *() on a String
+ // class. Doing so makes it too easy for a caller to get a char *
+ // to a temporary or have the pointer become deallocated memory
+ // at some later point unexpectedly.
+ char NameBuff[32];
+
+public:
+ static ProfileManager* Create();
+
+ // Static interface functions
+ unsigned GetProfileCount();
+ bool HasProfile(const char* name);
+ Profile* LoadProfile(const char* name);
+ Profile* GetDefaultProfile();
+ const char* GetDefaultProfileName();
+ bool SetDefaultProfileName(const char* name);
+
+ // The previous implementation seemed to imply that if you loaded
+ // some profiles for a given 'device type' and then saved changes,
+ // and then loaded a different device type, your changes would be lost
+ // because the cache would be cleared without ever being persisted to
+ // disk. The redesign fixes this by eliminating the 'per-device-type'
+ // profile mechanism.
+ //
+ // Profiles represent a users settings and they
+ // may have different settings for each device, so the profile should
+ // encapsulate all of them.
+ //
+ // Ideally, you should be able to query the top level profile for
+ // properties related to the hardware based on whatever you're using,
+ // rather than having to query specifically for generic, DK1 or DKHD
+ // but the class hierarchy makes that problematic.
+ bool Save(const Profile * profile);
+ bool Delete(const Profile * profile);
+
+ // Index based fetching is completely removed. I have no idea what
+ // was intended to be useful for. Perhaps it was intended as a
+ // potential optimization since querying by name is O(N) instead of
+ // O(1), but it's unfathomable that the performance difference would
+ // ever be noticeable, unless the caller is doing something pathological
+ // like calling using profile manager every frame, and unlikely even
+ // then
+protected:
+ ProfileManager();
+ ~ProfileManager();
+ void LoadCache();
+ void SaveCache();
+ void ClearCache();
};
@@ -240,4 +351,4 @@ String GetBaseOVRPath(bool create_dir);
}
-#endif // OVR_Profile_h \ No newline at end of file
+#endif // OVR_Profile_h