What’s the story on Couchbase Lite and Unity3D?

Some people might be wondering this as I (somewhat naively) dove into the task of taking on Unity3D as a freshman in my current company.  At that time, Unity3D was far behind the times with a .NET 3.5-ish runtime and support for C# 4 at best (making it over half a decade behind the times).  This became impossible to handle and so I was forced to put it on hold waiting for the promise of a more modern runtime that came around early 2016.  The first Unity3D release containing Mono 4.8 and support for the .NET 4.6 profile came and so how has the story changed?

First of all, I will put a disclaimer here that says me writing about this does not reflect on managerial decisions on what Couchbase will support, and that I am purely commenting from a technical standpoint.  That being said let’s see what happens when we just pluck off a certain build off of Nuget and throw it into the plugins folder (with the proper native dll alongside it).  So right now this is how the plugins folder looks (I’m doing this on macOS):

Screen Shot 2017-07-14 at 14.42.36

No dice…there are errors in Unity3D:

Screen Shot 2017-07-14 at 14.44.25

Hmmm what is going on here?  Well the problem is that Unity3D is running purely on the Mono implementation of .NET 4.6, and knows nothing about the .NET Standard ecosystem yet.  .NET Core split the majority of the frameworks that .NET Framework / Mono provided into Nuget packages, including some very low level stuff (like the System namespace).  This allows the platform to decide how it wants to handle everything with a lot of granularity.  Unfortunately, it also means that we fail to get this needed DLL.  Well, I like a good hack so let’s just grab a copy from Nuget and throw it into the project:

Screen Shot 2017-07-14 at 14.47.10.png

I find that a good way to “grab” these things is to open the Unity .sln project and add nuget references to it.  Unity will immediately rewrite the project so that the references get erased, but the packaged will remain as a neighbor folder to your Assets folder:

Screen Shot 2017-07-14 at 15.11.13

Celebration!  No more compilation errors, at least.  However, this is bound for failure because when installing Couchbase Lite, Nuget is supposed to take care of putting in the dependencies (like JSON .NET, Microsoft.Extensions.DependencyInjection, etc) so expect that this will fail at runtime and indeed it does with FileNotFoundException on various deps.  Sadly, after over a dozen dependencies put in from both the .NET Standard Library and other Nuget packages, Unity still fails at runtime with a quite unhelpful message:

Screen Shot 2017-07-14 at 14.49.54.png

I assume this is because Couchbase.Lite is a .NET Standard 1.4 DLL, and that means that .NET 4.6 is not supported (only 4.6.1+).  Ok fine…let’s try something else.  If I manipulate the Couchbase.Lite.csproj I have from the Couchbase Lite .NET GitHub repo to use <TargetFrameworks>netstandard1.4;net46</TargetFrameworks> then Visual Studio will build two DLLs for me, one for .NET Standard and one for .NET 4.6.  Let’s start over with the .NET 4.6 version, plus System.Runtime.dll.

I wrote a simple program to test out Couchbase and it looks like this:

Database.Delete("foo"null);
var db = new Database("foo");
db.InBatch(() =>
{
    for (int i = 0; i < 50; i++)
    {
        var doc = new Document($"doc{i}");
        doc.Set("number1", i);
        db.Save(doc);
    }
});
 
using(var q = Query.Select(SelectResult.Expression(Expression.Property("number1")))
               .From(DataSource.Database(db))
               .Where(Expression.Property("number1").GreaterThanOrEqualTo(25))) {
    using(var results = q.Run()) {
        foreach(var row in results) {
            Debug.unityLogger.Log("Test", row.GetInt(0));
        }
    }
}

So after replacing the .NET Standard version with the .NET 4.6 version, it compiled again and a lot more progress was made!  Now I am onto this error:

Screen Shot 2017-07-14 at 14.56.57.png

OH!  That’s an error from Couchbase Lite itself!  That means that it is properly running.  Remember in my last post when I talked about supporting a new platform with dependency injection?  Well this is a chance to do that!  Let’s add a default directory resolver for Unity3D!

 using System.IO;
 using Couchbase.Lite.DI;
 using UnityEngine;
 
 public class DefaultDirectoryResolver : IDefaultDirectoryResolver
 {
     public string DefaultDirectory()
     {
         return Path.Combine(Application.persistentDataPath, "CouchbaseLite");
     }
 }

And inside of the Start method of a Coroutine attached onto the main camera (the same place where I run the actual test program) add this at the top:

Service.RegisterServices(config =>
{
    config.AddSingleton<IDefaultDirectoryResolverDefaultDirectoryResolver>();
});

This will instruct Couchbase Lite to use the new implementation to find default directories for the database files.  Now let’s run again (note: AddSingleton is in the Microsoft.Extensions.DependencyInjection namespace, and we need to copy Microsoft.Extension.DependencyInjection.dll and Microsoft.Extensions.DependencyInjection.Abstractions.dll from Nuget.  After running and getting an exception I realize I also need Newtonsoft.Json.dll and Remotion.Linq.dll)!

Screen Shot 2017-07-14 at 15.03.46

Success!!  But actually, I want more information from Couchbase.  Let’s hook up the library logging to Unity’s output here.  I’ll write a simple logging provider (note:  this means I also need to grab Microsoft.Extensions.Logging.dll and Microsoft.Extensions.Logging.Abstractions.dll from Nuget):

 using System;
 using Microsoft.Extensions.Logging;
 using UnityEngine;
 
 public class UnityLoggingProvider : ILoggerProvider
 {
     public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
     {
         return new UnityLogger(categoryName);
     }
 
     public void Dispose()
     {
         
     }
 }
 
 internal sealed class UnityLogger : Microsoft.Extensions.Logging.ILogger
 {
     private readonly string _tag;
 
     public UnityLogger(string category)
     {
         _tag = category;
     }
 
     public IDisposable BeginScope<TState>(TState state)
     {
         return null;
     }
 
     public bool IsEnabled(LogLevel logLevel)
     {
         return true;
     }
 
     public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TStateExceptionstring> formatter)
     {
         switch(logLevel) {
             case LogLevel.Debug:
             case LogLevel.Information:
                 Debug.unityLogger.Log(_tag, formatter(state, exception));
                 break;
             case LogLevel.Warning:
                 Debug.unityLogger.LogWarning(_tag, formatter(state, exception));
                 break;
             case LogLevel.Error:
                 Debug.unityLogger.LogError(_tag, formatter(state, exception));
                 break;
 
         }
     }
 }

And register it underneath where I did the default directory resolver:

Service.RegisterServices(config =>
{
    config.AddSingleton<IDefaultDirectoryResolverDefaultDirectoryResolver>()
      .AddSingleton<ILoggerProviderUnityLoggingProvider>();
});

Screen Shot 2017-07-14 at 15.08.14.png

The absolute POWER!  Now the library is logging to the Unity debug console, and using Unity API to find a sane location to write to, despite it having no knowledge of it. I posted on the Unity forums and have been told that .NET Standard support is coming in the near future so I am looking forward to when that arrives, because I don’t think this is a supportable solution but if you are looking to play around then it’s workable!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s