1. Setup

Head over to our Quick Start Guide for instructions on how to install the API and configure your app. That will get you up and running in the editor. Once you're ready to make a build, a few more steps are necessary.

1.1 Setup Shaders

In order for our avatars to render properly you need to add the IMVU shaders to your Always Loaded Shaders. To do this, open Edit->Project Settings->Graphics, and add the shaders in the IMVUnity/Shaders folder that you intend to use to the Always Included Shaders list. If you're going to use Unlit materials, include the Unlit shaders. If you're going to use Lit materials, include the Lit shaders. If you're going to use PhysicallyLit materials, include the PhysicallyLit shaders. See the Unity documentation on Graphics Settings for more info on how to use these settings.

1.2 iOS / Android

For an iOS or Android build, you need to provide a custom protocol for the redirect URL. The protocol is the part of the URL that comes before the ://, so in a typical web URL like http://imvu.com, the protocol is http. You need to come up with a protocol which will be completely unique to your app. If there is another app on the device which uses the same protocol, the user will be prompted which app should handle the login redirect. This prompt will be confusing to the user, and if they choose wrong the login flow will fail. So make sure it's unique. It's recommended that it include both your company name and app name. If you're company is "Great Games" and you're making "Super Awesome Ball", then a good protocol might be great-games-super-awesome-ball.

The domain on the redirect URL, which is what comes directly after the ://, doesn't actually matter in this case, but there needs to be something there for it to be a valid URL. The URL must have a trailing slash after the domain. So if you're using the protocol above, your redirect URL would be something like great-games-super-awesome-ball://whatever/.

This redirect URL needs to be specified for your platform (iOS or Android, or both), both on the developer page on the website, and in the IMVU settings in Unity.

1.3 Android

In order to authenticate on Android, the AndroidManifest.xml must contain the proper protocol. The package installs an AndroidManifest.xml file in the /Assets/Plugins/Android/ directory, though if you have an existing Android build you may need to merge it with your existing AndroidManifest.xml. To do that, you'll need to copy the Activity with android:name="com.imvu.oauth.MainActivity" from our manifest to yours. If you do not have an existing Android build with it's own AndroidManifest.xml, you can just use the one we provide as a starting point.

The one change you need to make is to set the protocol you setup for the Android platform. Look for these lines in the manifest file:

<!-- Set your app's custom protocol on the next line -->
<data android:scheme="put-protocol-here" />

And change the put-protocol-here string to your custom protocol. So if your redirect URL is great-games-super-awesome-ball://whatever/, as in the example above, the protocol is great-games-super-awesome-ball, and you'll want to edit it to:

<!-- Set your app's custom protocol on the next line -->
<data android:scheme="great-games-super-awesome-ball" />

2. Example Code

This is a minimal example of using the API to load an avatar, once all the setup above is done.

using IMVU;

class TestLoader : LocalAssetLoader {
    void Start() {
        Imvu.Connect().Then(
            userModel => Load(userModel)
        ).MatchErr(
            error => Debug.LogError(error)
        );
    }
}

The connection process is handled for you when you call Imvu.Connect(). This returns a Promise<UserModel>. Promises here function very much like JavaScript promises, and they're documented in more detail at the end of this document. Then it calls Load(), a function inherited from LocalAssetLoader, which loads an IMVU avatar from a UserModel onto this GameObject. This returns a Promise<AssetInfo>. The avatar will already be setup to work with Mecanim, so if you've placed an Animator on it it'll already be animating.

If there are any actual errors at either step, it will fall through to the Catch() and log the error.

3. REST

The REST APIs, at their root, are a set of HTTP-based APIs for accessing IMVU. Inside the APIs this is hidden behind a clean, strongly-typed API. Aside from the fact that many operations are asynchronous, you don't have to worry about the fact that there are HTTP requests behind the scenes. The REST APIs are accessed through RestModel and RestCollection classes.

3.1 RestModel Class

A RestModel is a base class for representing a single data node. It is generic over the type it stores. It contains an info member variable of that type. For instance, UserModel is a subclass of RestModel<UserInfo>, and its info member variable will have type UserInfo.

Each subclass of RestModel will also contain getters for each of the other data nodes related to it. These getters will always return another RestModel.

The set of RestModels that exist right now are: * UserModel - Data about an IMVU user. * OutfitModel - Data about a saved outfit. * FeedElementModel - Data about a single feed post. * ProductModel - Data about an IMVU product.

3.2 RestCollection Class

A RestCollection is a base class for representing a list of data nodes. This looks like:

class RestCollection<T> : RestModel<CollectionInfo<T>> {
    public CollectionInfo<T> info;

    public Promise<Unit> NextPage();
    public delegate void AddEventHandler(T model);
    public event AddEventHandler add;
}

The CollectionInfo class is as follows:

class CollectionInfo<T> {
    public List<Promise<T>> items;
    public int totalCount;
}

It's generic over the type of RestModel it stores. Like RestModel, it contains an info member variable, with the type RestCollection, generic over the same type. For example, the FriendCollection inherits from RestColletion<UserModel>, and it's info member variable will have type CollectionInfo<UserModel>.

Note that collections are paginated. The totalCount field represents the total size of the collection, which may be larger than the number currently loaded, which is why it needs to be a separate field instead of just using items.Count. Calling NextPage() will load the next page. The promise it returns will be accepted when the request for the next page is complete and populated into the collection, or rejected if the request fails. There is also the add event, which will be called each time a new element is added to the collection. Currently this only happens during loading. So you can respond to a new page loading either by using the promise returned by NextPage, or one element at a time by using the add event.

The set of RestCollections that exist right now are:

  • FriendCollection - A collection of UserModels, obtained by calling UserModel.GetFriends().
  • OutfitCollection - A collection of OutfitModels, obtained by calling UserModel.GetOutfits().
  • FeedElementCollection - A collection of FeedElementModels, obtained by calling UserModel.GetPersonalFeed(), UserModel.GetSubscribedFeed() or UserModel.GetRecommendedFeed().
  • ProductCollection - A collection of ProductModels, obtained by calling OutfitModel.GetProducts() or AvatarModel.GetProducts().

3.3 UserModel Class

This model represents a user:

class UserModel : RestModel<UserInfo> {
    public UserInfo info;

    // Get the user's friends
    public Promise<FriendCollection> GetFriends(int limit = 0);
    // Get the user's current look
    public Promise<ProductCollection> GetCurrentLook();
    // Get the user's profile look
    public Promise<ProductCollection> GetProfileLook();
    // Get the user's saved outfits
    public Promise<OutfitCollection> GetOutfits();
    // Get the user's personal feed posts
    public Promise<FeedElementCollection> GetPersonalFeed();
    // Get the user's subscribed feed, which is the aggregate of their posts and their friends' posts
    public Promise<FeedElementCollection> GetSubscribedFeed();
    // Get the user's recommended feed, which is a collection of whitelisted posts
    public Promise<FeedElementCollection> GetRecomendedFeed();
}

The limit argument to GetFriends determines the page size of the resulting FriendCollection. The default value of 0 means to use the server's default page size.

The difference between the current look and the profile look is that current look should be used whenever the user is actually present, and the profile look when they're not. The current look reflects what they last put on while using dress up in the IMVU client, website or mobile app, while the profile look is the look they've saved to their profile. Note that the current look can only be accessed on the UserModel for the logged in user. If called on any other user, it will return an error. The only way to access the current look of another user is through the PhotonAssetLoader or UNetAssetLoader, which will synchronize that user's current look when they connect to a synchronous multiplayer experience.

The UserInfo data is as follows:

struct UserInfo {
    public string gender;
    public string country;
    public string state;
    public Uri thumbnailUrl;
    public bool online;
    public string username;
    public int id;
}

You can use the thumbnailUrl URL to load the user's profile image. It's recommended to use the TextureLoader for this purpose.

3.4 OutfitModel class

This model represents an outfit, which is a look plus a name and an image:

class OutfitModel : RestModel<OutfitInfo> {
    // Get the list of products making up the outfit's look
    public Promise<ProductCollection> GetProducts();

    public OutfitInfo info;
}

The OutfitInfo data is as follows:

public struct OutfitInfo {
    public string name;
    public Uri image;
    public List<int> pids;
}

You can use the image URL to load an image of the outfit. It's recommended to use the TextureLoader for this purpose.

3.5 ProductModel class

This model represents an IMVU product:

class ProductModel : RestModel<ProductInfo> {
    public ProductInfo info;
}

The ProductInfo data is a follows:

public struct ProductInfo {
    public int pid;
    public string name;
    public string creatorName;
    public string rating;
    public Uri productPage;
    public Uri creatorPage;
    public string gender;
    public List<string> categories;
    public List<string> types;
    public Uri previewImage;
}

The rating field is one of GA or AP, which determines whether the product is appropriate for all audiences (GA or General Audience), or require an access pass (AP for Access Pass).

The gender and categories fields are set by the creator. They will usually reflect the proper nature of the product, but they aren't guaranteed to be correct.

The types field is a list of additional categories which are set programatically, and these are reliably accurate.

3.6 FeedElementModel Class

This model represents a feed post:

class FeedElementModel : RestModel<FeedElementInfo> {
    public FeedElementInfo info;

    // Get the user who made this post
    public Promise<UserModel> GetUser();
}

The FeedElementInfo is as follows:

struct FeedElementInfo {
    public string time;
    public string type;
    public FeedElementPayloadInfo payload;
}

public struct FeedElementPayloadInfo {
    public int height;
    public int width;
    public Uri url;
    public Uri thumbnailUrl;
    public string message;
}

The type field is one of message or photo. If it's a message post, then the only field of the payload that will be populated is message. If it's a photo post, then all the fields will be populated. You can use the url and thumbnailUrl URLs to load the attached image for a photo post. It's recommended to use the TextureLoader for this purpose.

3.7 FriendCollection Class

This collection represents a user's friend list:

class FriendCollection : RestCollection<UserModel> {
    public CollectionInfo<UserModel> info;
}

3.8 OutfitCollection class

This collection represents a set of saved outfits:

class OutfitCollection : RestCollection<OutfitModel> {
    public CollectionInfo<OutfitModel> info;
}

3.9 FeedElementCollection Class

This collection represents a set of feed posts, generally just called a feed:

class FriendElementCollection : RestCollection<FeedElementModel> {
    public CollectionInfo<FeedElementModel> info;

    // Post an image to this feed
    public Promise<FeedElementModel> PostImage(Texture2D image);
}

Note that PostImage() is only valid on the personal feed of the currently logged-in user. If you call it on any other feed, it will fail. This call will pop up a confirmation dialog showing the user the image you intend to post, so you can not post to the user's feed without their consent. If they choose not to post it, the promise will resolve to an error.

3.10 ProductCollection class

This collection represents the set of products in a particular look:

class ProductCollection {
    public CollectionInfo<ProductModel> info;
}

3.11 Imvu Class

The entry point to the whole API is the Imvu class. For the moment, this has one method:

class Imvu {
    public static Promise<UserModel> Connect(bool storeToken = false);
    public static void Disconnect();
}

The Connect() method will handle the whole connection process for you. By default, the oauth token will be stored, so the next time the app is run it will automatically be logged in to the same account. If you don't want this behavior, then pass in false for storeToken.

If you call Connect() multiple times, it will always return the same promise, unless the user cancelled or the connection failed. This means that if you need the logged-in user's UserModel in multiple places, you can call Connect() from each of those places independently. When the user cancels or the connection fails, the stored promise is cleared, which allows you to attempt to connect again.

The Disconnect() method will clear the oauth token. All stored RestModels will stop functioning, and should no longer be used. The next time Connect() is called, it will go through the connection process again, and return a new UserModel.

4. Asset Loading

In order to load an IMVU asset, you need to create a GameObject and put a LocalAssetLoader on it. If you want this loading to synchronize properly in a multiplayer experience, you'll also need a UNetAssetLoader or a PhotonAssetLoader, depending on whether you're using UNet or Photon for your network layer.

4.1 LocalAssetLoader

The LocalAssetLoader is a MonoBehaviour. If it's placed on a GameObject, you can then call its Load() method to load that asset onto that GameObject. Its signature is:

class LocalAssetLoader : MonoBehaviour {
    Promise<AssetInfo> Load(UserModel user, LoadOptions options = null);
    Promise<AssetInfo> Load(OutfitModel outfit, LoadOptions options = null);
}

The optional LoadOptions argument can override the current load settings. See section 4.5 LoadOptions for more info.

While the LocalAssetLoader can be used directly, it's also common to subclass it, like so:

class MyAvatar : IMVU.LocalAssetLoader {
    void Start() {
        Imvu.Connect().Then(
            userModel => Load(userModel, Setup)
        ).Then(
            assetInfo => {
                // do stuff with the avatar
            }
        );
    }
}

4.2 UNetAssetLoader

The UNetAssetLoader has the same API as the LocalAssetLoader:

class UNetAssetLoader : NetworkBehaviour {
    Promise<AssetInfo> Load(UserModel user, LoadOptions options = null);
    Promise<AssetInfo> Load(OutfitModel outfit, LoadOptions options = null);
}

The optional LoadOptions argument can override the current load settings. See section 4.5 LoadOptions for more info.

The main feature of the UnetAssetLoader is that if Load() is called while connected to a UNet server, it will cause the same asset to be loaded on the matching GameObject on all clients.

4.3 PhotonAssetLoader

The PhotonAssetLoader has the same API as the LocalAssetLoader:

class PhotonAssetLoader : Photon.MonoBehaviour {
    Promise<AssetInfo> Load(UserModel user, LoadOptions options = null);
    Promise<AssetInfo> Load(OutfitModel outfit, LoadOptions options = null);
}

The optional LoadOptions argument can override the current load settings. See section 4.5 LoadOptions for more info.

The main feature of the PhotonAssetLoader is that if Load() is called while connected to a Photon server, it will cause the same asset to be loaded on the matching GameObject on all clients.

NOTE: When using the Photon demo you need to apply your Photon AppID to the demo and you need to turn on "Auto-Join Lobby". Go to window->Photon Unity Networking->PUN Wizard->Locate Photon Server Settings. Ensure your Photon AppID is here and "Auto-Join Lobby" is turned on. "Auto-Join Lobby" can also be turned on programmatically. This setting is important to be aware of.

4.4 AssetInfo

The AssetInfo class contains basic information about the asset, along with some high level animation control:

class AssetInfo {
    public List<SkeletonData> skeletons;
    public GameObject avatarObject;
    public List<GameObject> attachmentObjects;

    public void PlayAttachmentIdleActions();
    public void PauseAllIdles();
    public void ResetAllIdles();
}

The skeletons field is a list of the skeletons on the asset. The first is always the root skeleton. After that come the skeletons of each of the attachments. The SkeletonData provides the following:

public class SkeletonData {
    public Transform root;
    public Transform[] bones;

    // Check if a bone exists, by name
    public bool HasBone(string bname);
    // Get a bone, by name
    public Transform GetBone(string bname);
    // Get the index of a bone, by name
    public int GetBoneIndex(string bname);
}

This allows you to attach geometries or additional behaviours to bones. For instance, it's used heavily by the Equip demo, to attach equipment to bones, or the Ragdoll demo, to attach physics to bones.

The avatarObject and attachmentObjects fields contain the GameObjects corresponding to the base avatar and its accessories (if any). Accessories will tend to be things like tails, wings, pets, etc.

Then there are some utility functions to provide for common use cases with animations:

  • PlayAttachmentIdleActions - Start idles on all attachments that have loaded idle animations. It shouldn't be necessary to call this function if the "Automatically run idle animations on attachments" option is enabled in the loader settings. This function can be used to start idles if not specified in the settings or to restart idle animations that have previously been paused.

  • PauseAllIdles - Freeze all active idles on the current frame. If the animations are currently paused then calling PauseAllIdles() or PlayAttachmentIdleActions() will cause them to resume.

  • ResetAllIdles - Reset the idle animations to the first frame of the first clip. Paused idles will remain paused when reset - active idles will continue to play from that point.

For more info on animations, see section 6. Animations.

4.5 LoadOptions

The LoadOptions class can be provided as an optional argument to all of the various Load functions on LocalAssetLoader, UNetAssetLoader and PhotonAssetLoader, in order to override the normal default load settings. It's constructor looks like:

public LoadOptions(
    MaterialType? materialType = null,
    List<LodSetting> lodSettings = null,
    bool? loadImvuAnimations = null,
    bool? autoStartBlinks = null,
    bool? autoStartAttachmentIdles = null
    );

Since these are both optional arguments, you can specify only the subset you wish to change, as so:

LoadOptions(materialType: MaterialType.Unlit);
LoadOptions(lodSettings: new List<LodSetting> { new LodSetting(LodLevel.Low, 100) });
LoadOptions(
    materialType: MaterialType.Unlit,
    lodSettings: new List<LodSetting> { new LodSetting(LodLevel.Low, 100) },
    loadImvuAnimations: true);

LodSetting and MaterialType are described below and animation related flags are described in section 6.1 Animation Settings.

4.6 Level of Detail

The API supports loading asset meshes at several different levels of detail and can set up loaded GameObjects to automatically switch between them depending on the percentage of screen space that they occupy. This lets you display assets with dense meshes when close up, when detail is needed, but then switch to meshes with fewer triangles when at a distance, to improve drawing performance.

By default, they are configured to load each GameObject with several levels of detail and automatically switch between them, which has obvious benefits. However, there are cases when you may want to specify custom level-of-detail settings:

  • If you specify fewer levels of detail than the default, the load will make fewer network requests for mesh data, saving some loading time.
  • If you specify just one level of detail, the load can omit the LodComponent that does the automated switching between levels, saving some per-frame CPU overhead.
  • Custom settings also let you tune the default transition points for display quality or performance.

You can customize the default level of detail settings in the IMVU Settings window, or override the defaults at run time, as detailed in section 5.3 Load Settings. You can also use the LoadOptions described in section 4.5 LoadOptions to override the defaults on a specific asset.

Custom level-of-detail settings are specified by a list of LodSetting objects. Each LodSetting object specifies a LodLevel and a maximum screen coverage percentage. There are four LodLevels available:

  • LodLevel.Maximum - The most detailed mesh, as originally authored.
  • LodLevel.Medium - An automatically generated mesh with lower detail (about 50%).
  • LodLevel.Low - An automatically generated mesh with very low detail (about 10%).
  • LodLevel.Hidden - Displays no meshes at all.

Note that the actual amount of detail reduction depends on the geometry of the mesh. For some models, reduction may not be possible, in which case the original mesh is used for all levels.

Screen coverage percentage is expressed as a float from 0-100. At runtime, the screen coverage is estimated from the bounding boxes of the meshes, and the level with the smallest coverage value greater than the object's current coverage (capped to 100%) is used. If you do not specify levels up to 100% coverage, the maximum level will be automatically included.

For example, if we were loading an asset that we know will never be displayed in detail, we could save some network bandwidth by omitting the maximum level when loading, using these LoD settings:

new List<LodSetting>() {
    new LodSetting(LodLevel.Hidden, 0.01f),
    new LodSetting(LodLevel.Low, 1.0f),
    new LodSetting(LodLevel.Medium, 100.0f),
}

Or, if we were loading an asset to only be displayed in detail, we could save some network requests and runtime CPU by loading just the maximum level, with these LoD settings:

new List<LodSetting>() {
    new LodSetting(LodLevel.Maximum, 100.0f),
}

4.7 Material Types

There are three different types of shaders available for the avatars:

  • Unlit - These are the simplest shaders. They have no lighting and no shadows. They will tend to be the fastest.
  • Lit - These shaders use the Unity legacy lighting system. They are lit and recieve and cast shadows.
  • PhysicallyLit - These use Unity 5's new lighting system. They are more advanced, but IMVU avatars can't take advantage of most of the additional features they provide, and there are some bugs in how some avatars render on certain platforms with these shaders. This option is not currently recommended.

You can customize the default material type in the IMVU Settings window, or override the defaults at run time, as detailed in section 5.3 Load Settings. You can also use the LoadOptions described in section 4.5 LoadOptions to override the defaults on a specific asset.

4.8 TextureLoader

The TextureLoader facilitates the loading images over HTTP into a Texture2D. This is easy to do if you use the WWW class directly, but if you use TextureLoader you'll get the benefits of our network stack, which is much more reliable. The TextureLoader API is as follows:

class TextureLoader {
    public Promise<Texture2D> Load(Uri url);
}

5. Settings

The IMVU Settings window can be accessed by going to Window -> IMVU Settings. This window is divided vertically into three sections: the clear buttons, the oauth settings and the load settings.

5.1 Clear Buttons

  • Disconnect - If you run your app from within the editor, and connect, then your oauth token will be stored in the local store, via Unity's PlayerPrefs. This is normally a convenience, as it means you don't have to connect again each time you play. However, if you need to clear that connection data, you can come here and press the Disconnect button.
  • Clear Load Settings - There are calls you can make at runtime, which are detailed below, which allow you to change the default load settings. When you do so, you can choose to have those detaults stored in the local store, via Unity's PlayerPrefs, to override the defaults configured in this window. That still works when running the app in the editor. This button will clear those defaults.

5.2 OAuth Settings

This lists a set of platforms, and for each platform you can provide an App ID and a Redirect URL.

  • App ID - The ID of the app you wish to identify as. This can be garnered from the developer page, as detailed in Setup above. Note that there is a different App ID for each platform. For iOS, Android or WebGL, be sure to use App IDs from those platforms. The Editor, Windows and OS X builds exist only for development and debugging purposes, and should get the App ID associated with whichever platform you're focusing on developing for at the time.
  • Redirect URL - The URL that the OAuth token will be forwarded to after the user logs in. This needs to be one of the URLs configured on for the app on the Developer page. For the Editor, Windows and OS X, the redirect URL needs to be http://localhost:XXXX, where the XXXX is a port number. We use 8888, but any number which is not in use on your development machine can work, provided it's consistent with the URL entered on the developer page.

5.3 Load Settings

This specifies the default settings used to load an IMVU asset. Any of these can be overriden when loading specific assets, as described in section 4.5 LoadOptions. The defaults can also be changed at run time, and these changes can optionally be stored into the local store via Unity's PlayerPrefs. This allows you to make these settings configurable by the user, if desired.

To change the settings at runtime, these functions are provided in the Settings class:

public static void SetDefaultLodSettings(List<LodSetting> lodSettings, bool save = false);
public static void SetDefaultMaterialType(MaterialType matType, bool save = false);
public static void SetDefaultLoadImvuAnimations(bool state, bool save = false);
public static void SetDefaultAutoStartBlinks(bool state, bool save = false);
public static void SetDefaultAutoStartAttachmentIdles(bool state, bool save = false);

In these functions the optional save argument determines whether the change with be saved to the local store. If it's not specified, the change will not be saved.

See sections 4.6 Level of Detail and 4.7 Material Types for detailed descriptions of what the Default LOD Settings and Default Material Type settings mean. For the three animation related flags:

  • Load IMVU Animations - Controls the download of animation data from the IMVU servers. If deselected then avatars may load slightly faster, but attachment idles and blink animations will not be available. Note that there exist some avatars in the IMVU world that contain an inordinate number of animations. In these cases, a noticeable difference in load times may be observed.

  • Automatically run blink animations - If animations are loaded and this option is selected then the avatar will automatically start blinking after loading completes. This behavior can be toggled via API calls available on the ImvuAnimator component. Note that some avatars do not contain a blink animation, in which case this option will do nothing.

  • Automatically run idle animations on attachments - If animations are loaded then this option causes the idle animations to automatically run on all animated avatar attachments, such as tails, wings and pets. Idle animations can also be started, paused, and reset via API calls.

6. Animation

IMVU avatars come bundled with a set of animations that are used in the IMVU ecosystem. These are divided into two groups: skeletal animations and morph animations.

  • Skeletal animations move the skeleton of the avatar or one of it's attachments, just like any normal Unity animation.

  • Morph animations apply offsets to the vertices of a mesh. They're used primarily for facial expressions, though they're occassionally used for a variety of other purposes as well. Unity does not come with any equivalent to morphs. The blink animation is implemented as a morph.

There are a number of utility functions for common animation use cases on the AssetInfo. See section 4.4 Asset Loader.

6.1 ImvuAnimator

Both the avatar GameObject and the associated attachment GameObjects will be assigned a ImvuAnimator component, which lets you control the animations on that avatar or attachement.

class ImvuAnimator: MonoBehaviour {
    public void SetBlinksActive(bool state);
    public bool GetBlinksActive();
    public bool HasIdleActions();
    public void PlayDefaultIdleActions();
    public bool GetDefaultIdlesActive();
    public bool IsActive();
    public void Reset();
    public bool IsPaused();
    public void TogglePause();
}
  • SetBlinksActive - Enables or disables the blink animation. The "Automatically run blink animations" option will determine whether blinks are active by default, but this function will allow you to change whether they're running at runtime. Note that if you want to enable or disable blinking on the avatar and all it's attachements at once, you should use the function on AssetInfo. This function allows you to toggle it for just the avatar or a specific attachment. Blinking is most often relevant only for the avatar, but some attachments, particularly pets, will also have the ability to blink.
  • GetBlinksActive - Returns true if the blink animations are currently active on this avatar or attachment.
  • HasIdleActions - Returns true if this avatar or attachment has idle animations.
  • PlayDefaultIdleActions - If idle animations are present on this avatar or attachment, this will run them. See 'PlayAttachmentIdleActions()' for caveats.
  • GetDefaultIdlesActive - Has 'PlayDefaultIdleActions()' been enabled for this object?
  • IsActive - Are any (IMVU) animations current running on this object?
  • Reset - Reset all active animations to the first frame of the first clip.
  • IsPaused - Are the animations currently paused?
  • TogglePause - If not pause, then pause. It already paused, then resume playback.

7. Common Classes

The Result and Promise classes may be confusing to many Unity developers. For a more thorough treatment of how they work and why they're useful, see this post: http://engineering.imvu.com/2015/12/11/promises-in-unity/

7.1 Error Class

The Error class just encodes a human-readable error message with a machine-readable error code. The error code is typically in the form <SYSTEM>-<NUM>, where <SYSTEM> is the system the error occurred in, and <NUM> is a number that will uniquely identify the error within that system. For instance, errors coming from the LocalAssetLoader are in the form LAL-01, LAL-02, etc. If the same error occurs in multiple places, they should still have unique numbers, in order to aid debugging.

Class Layout

class Error {
    public string message;
    public string code;

    public Error(string message, string code);
}

7.2 Result Class

A Result represents a value that may or may not exist. If it doesn't exist, it houses an error explaining why. It's generic over the type of the value. In order to maintain type safety, the contents of the Result are private. They're accessed through lambdas with the Match(), MatchVal(), MatchErr(), Then() or Catch() methods.

In proper computer science terms: Result is a sum type, with simulated pattern matching through the Match(), MatchVal(), MatchErr(), Then() and Catch() methods.

Class Layout

public class Result<ValueT> {
    public static Result<ValueT> Val(ValueT val);
    public static Result<ValueT> Err(Error err);
    public bool HasValue();
    public bool HasError();
    public RetT Match<RetT>(Func<ValueT, RetT> valFunc, Func<Error, RetT> errFunc);
    public void Match(Action<ValueT> valFunc, Action<Error> errFunc);
    public void MatchVal(Action<ValueT> valFunc);
    public RetT MatchVal<RetT>(Func<ValueT, RetT> valFunc, RetT defaultValue);
    public void MatchErr(Action<Error> errFunc);
    public RetT MatchErr<RetT>(Func<Error, RetT> errFunc, RetT defaultError);
    public Result<NewValT> Then<NewValT>(Func<ValueT, Result<NewValT>> valFunc);
    public Result<NewValT> Then<NewValT>(Func<ValueT, Result<NewValT>> valFunc, Func<Error, Result<NewValT>> errFunc);
    public Promise<NewValT> Then<NewValT>(Func<ValueT, Promise<NewValT>> valFunc);
    public Promise<NewValT> Then<NewValT>(Func<ValueT, Promise<NewValT>> valFunc, Func<Error, Promise<NewValT>> errFunc);
    public Result<ValueT> Catch(Func<Error, Result<ValueT>> errFunc);
    public Promise<ValueT> Catch(Func<Error, Promise<ValueT>> errFunc);
}

Result.Val

public static Result<ValueT> Val(ValueT val)

This static function will construct a new result with a value.

Example:

var result = Result<int>.Val(42);

Result.Err

public static Result<ValueT> Err(Error err)

This static function will construct a new result with an error.

Example:

var result = Result<int>.Err(new Error("The operation failed", "EX-1"));

Result.HasValue

public bool HasValue();

Returns true if the Result has a value, or false otherwise.

Result.HasError

public bool HasError();

Returns true if the Result has an error, or false otherwise.

Result.Match

void Match(Action<ValueT> valFunc, Action<Error> errFunc);
RetT Match<RetT>(Func<ValueT, RetT> valFunc, Func<ValueT, RetT> errFunc);

Match() will take two functions arguments. If it has a value, it will execute the first function, passing it the value. If it has an error, it will execute the second function, passing it the error. This guarantees you will never accidentally access the wrong field on the Result.

The first overload takes functions with no return type. The second overload takes functions with matching return types. In this case, whichever function is run will have it's return type returned by Match(). Note that it's necessary that both functions have the same return type, because Match() can only have one return type.

Example:

IMVU.Result<int> result = FunctionThatMightFail();
result.Match(
    val => Debug.Log("Success! Value: " + val),
    err => Debug.LogError(err)
);

Result.MatchVal

public void MatchVal(Action<ValueT> valFunc);
public RetT MatchVal<RetT>(Func<ValueT, RetT> valFunc, RetT defaultValue);

MatchVal() takes a funciont which will only be executed if the Result currently has a value.

The first overload takes a function with no return type. The second overload takes a function with a return type, and a default value. If the function is called, it's return value will be returned by MatchVal(). Otherwise, it will return the default value.

Example:

IMVU.Result<int> result = FunctionThatMightFail();
result.MatchVal(
    val => Debug.Log("Success! Value: " + val)
);

Result.MatchErr

public void MatchErr(Action<Error> errFunc);
public RetT MatchErr<RetT>(Func<Error, RetT> errFunc, RetT defaultError);

MatchErr() takes a funciont which will only be executed if the Result currently has an error.

The first overload takes a function with no return type. The second overload takes a function with a return type, and a default error. If the function is called, it's return value will be returned by MatchErr(). Otherwise, it will return the default error.

Example:

IMVU.Result<int> result = FunctionThatMightFail();
result.MatchErr(
    err => Debug.LogError(err)
);

Result.Then

public Result<NewValT> Then<NewValT>(Func<ValueT, Result<NewValT>> valFunc);
public Result<NewValT> Then<NewValT>(Func<ValueT, Result<NewValT>> valFunc, Func<Error, Result<NewValT>> errFunc);
public Promise<NewValT> Then<NewValT>(Func<ValueT, Promise<NewValT>> valFunc);
public Promise<NewValT> Then<NewValT>(Func<ValueT, Promise<NewValT>> valFunc, Func<Error, Promise<NewValT>> errFunc);

Then() takes one or two function arguments. The versions that take two function arguments will execute the first function if the Result has a value, and the second one if it has an error. The versions that take only one function argument will execute that function only if the Result has a value. These functions must return a Result or a Promise, which will in turn by returned by Then(). In the single argument versions, if there is an error result, then a new Result or Promise is generated with that error and returned.

Then() is designed for chaining. Since it always returns a new Result or Promise, you can always call Then() or Catch() again on the output. Unhandled errors also fall through to the next stage in the chain, allowing any errors that happened in the chain to fall through to a common error handler at the end.

Result.Catch

public Result<ValueT> Catch(Func<Error, Result<ValueT>> errFunc);
public Promise<ValueT> Catch(Func<Error, Promise<ValueT>> errFunc);

Catch() takes a function argument, which will be executed only if the Result has an error. This function must return a new Result or Error, which will in turn be returned by Catch(). If the Result contains a value, then that value will be packaged into a new Result or Error and returned.

Catch() is designed for chaining, to be used in conjunction with Then(). Since it always returns a new Result or Promise, you can always call Then() or Catch() again on the output. Unhandled values also fall through to the next stage in the chain.

7.3 Promise Class

A Promise represents a value that may not exist yet. This is designed to function very much like a JavaScript promise, even mirroring its API as closely as possible. Think of it as an asynchronous Result. The difference is that when you give it functions, they won't be called until the Promise is resolved. If the Promise is already resolved, they'll be called immediately.

Class Layout

    public class Promise<ValueT> {
        public Promise();
        public Promise(Result<ValueT> _value);
        public Promise(Promise<ValueT> promise);
        public static Promise<ValueT> Val(ValueT val);
        public static Promise<ValueT> Err(Error err);
        public void Match(Action<ValueT> valFunc, Action<Error> errFunc);
        public void MatchVal(Action<ValueT> valFunc);
        public void MatchErr(Action<Error> errFunc);
        public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Result<NewValueT>> valFunc);
        public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Result<NewValueT>> valFunc, Func<Error, Result<NewValueT>> errFunc);
        public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Promise<NewValueT>> valFunc);
        public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Promise<NewValueT>> valFunc, Func<Error, Promise<NewValueT>> errFunc);
        public Promise<ValueT> Catch(Func<Error, Result<ValueT>> errFunc);
        public Promise<ValueT> Catch(Func<Error, Promise<ValueT>> errFunc);
        public void Resolve(Result<ValueT> result);
        public void Accept(ValueT acceptValue);
        public void Reject(Error rejectValue);
        public static Promise<List<ValueT>> All(List<Promise<ValueT>> promises);

Constructors

public Promise();
public Promise(Result<ValueT> _value);
public Promise(Promise<ValueT> promise);

The default constructor (with no arguments) constructs an unresolved promise. The Result constructor will create an already-resolved promise using the value of the Result. It's the equivalent of calling the default constructor then calling Resolve(). The Promise constructor makes this Promise's state mirror the one that's passed in, so when that Promise is resolved, this new one will be as well, with the same resolution.

Promise.Val

public static Promise<ValueT> Val(ValueT val)

This static function will construct a new Promise that's already accepted.

Example:

var result = Promise<int>.Val(42);

Promise.Err

public static Promise<ValueT> Err(Error err)

This static function will construct a new Promise that's already rejected.

Example:

var result = Promise<int>.Err(new Error("The operation failed", "EX-1"));

Promise.Match

void Match(Action<ValueT> valFunc, Action<Error> errFunc);

Match() will take two functions arguments, one of which will be called when the Promise resolves. If it's accepted, the first will be called. If it's rejected, the second will be called.

Example:

IMVU.Promise<int> promise = AsynchronousFunction();
promise.Match(
    val => Debug.Log("Success! Value: " + val),
    err => Debug.LogError(err)
);

Promise.MatchVal

public void MatchVal(Action<ValueT> valFunc);

MatchVal() takes a funcion which will executed when and if the Promise is accepted.

Example:

IMVU.Promise<int> promise = AsynchronousFunction();
promise.MatchVal(
    val => Debug.Log("Success! Value: " + val)
);

Promise.MatchErr

public void MatchErr(Action<Error> errFunc);

MatchErr() takes a funcion which will be executed when and if the Promise is rejected.

Example:

IMVU.Promise<int> promise = AsynchronousFunction();
promise.MatchErr(
    err => Debug.LogError(err)
);

Promise.Then

public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Result<NewValueT>> valFunc);
public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Result<NewValueT>> valFunc, Func<Error, Result<NewValueT>> errFunc);
public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Promise<NewValueT>> valFunc);
public Promise<NewValueT> Then<NewValueT>(Func<ValueT, Promise<NewValueT>> valFunc, Func<Error, Promise<NewValueT>> errFunc);

Then() takes one or two function arguments, which will be called when the Promise is resolved, or called immediately if the Promise is already resolved. The versions that take two function arguments will execute the first function if the Promise is accepted, and the second one if it's rejected. The versions that take only one function argument will execute that function only if it's accepted. These functions must return a Result or a Promise, which will turn into a Promise returned by Then(). In the single argument versions, if the Promise is rejected, then a new Promise is generated with the same error and returned.

Then() is designed for chaining. Since it always returns a new Promise, you can always call Then(), Catch(), Match(), MatchVal() or MatchErr() again on the output. Unhandled errors also fall through to the next stage in the chain, allowing any errors that happened in the chain to fall through to a common error handler at the end.

Examples:

AsynchronousFunction().Then(
    val => val.GetAnotherAsyncThing()
).Match(
    val => Debug.Log("Async thing: " + val),
    error => Debug.LogError(error)
);
AsynchronousFunction().Then(
    val => user.GetAnotherAsyncThing(),
    error => {
        Debug.LogError("AsynchronousFunction failed: " + error);
        return Promise<OutfitCollection>.Err(error);
    }
).Match(
    val => Debug.Log("Async thing: " + val),
    error => Debug.LogError("GetAnotherAsyncThing failed: " + error)
);

Promise.Catch

public Promise<ValueT> Catch(Func<Error, Result<ValueT>> errFunc);
public Promise<ValueT> Catch(Func<Error, Promise<ValueT>> errFunc);

Catch() takes one function argument, which will be called when the Promise is resolved, or called immediately if the Promise is already resolved, but only if the Promise is rejected. If the Promise is accepted, the new Promise returned by Catch() will also be accepted with the same value.

Catch() is designed for chaining. Since it always returns a new Promise, you can always call Then(), Catch(), Match(), MatchVal() or MatchErr() again on the output. Since Catch() only handles errors, successes will fall through allowing them to be handled further down the chain.

Example:

AsynchronousFunction().Catch(
    error => {
        Debug.Log("First attempt at AsynchronousFunction failed. Trying again.");
        return AsynchronousFunction();
    }
).Match(
    val => Debug.Log("Async thing: " + val),
    error => Debug.LogError("GetAnotherAsyncThing failed: " + error)
);

Promise.Resolve

public void Resolve(Result<ValueT> result);

Resolve() resolves the promise, with a Result. If the Result has a value, the Promise will be accepted. If it doesn't have a value, the Promise will be rejected.

Example:

var promise = new Promise<int>();
promise.Resolve(Result<Int>.Val(42));

Promise.Accept

public void Accept(ValueT acceptValue);

Accept() accepts the promise, with the value passed in.

Example:

var promise = new Promise<int>();
promise.Accept(42);

Promise.Reject

public void Reject(Error rejectValue);

Reject() rejects the promise, with the value passed in.

Example:

var promise = new Promise<int>();
promise.Reject(new Error("Error! Failure! Panic!", "EX-1"));

Promise.All

public static Promise<List<ValueT>> All(List<Promise<ValueT>> promises);

All() takes a list of promises and results a Promise to a list of values. If all of the input Promises are accepted, the resulting Promise will be accepted, and will compile all of the values from all of the promises into a list, as the value for the new Promise. It will maintain the order of the list when doing so. If any one of the Promises passed in is rejected, the resulting Promise will be rejected. The error used will be the error from the first Promise in the list to be rejected.

Example:

var promises = new List<Promise<int>> {
    GetPromise(),
    GetPromise(),
    GetPromise()
};
Promise<int>.All(promises).Match(
    values => {
        foreach (var value in values) {
            Debug.Log("Got value " + value);
        }
    },
    error => Debug.LogError("One of the promises failed: " + error)
);