2017/10/24

Return to Xamarin and So Long to PCL

Working on a new project and using C# for the majority of the PC client. We need a mobile app as well, so Xamarin seemed like the logical choice to maximize code reuse.

When previously working with Xamarin I was introduced to PCL. But, I was forever running into problems with platforms I was targeting and the ones supported by packages on nuget.

Apparently well aware of its flaws Microsoft has since deprecated PCL in favor of .Net Standard. We've started targeting the bleeding edge .Net Standard 2.0 since it provides the widest API coverage and meets our platform requirements. What follows is my experience getting a multi-platform (initially just Android and UWP) Xamarin.Forms app using .Net Standard 2.0 building and running with Visual Studio 2017 Community.


Creation

Using the VS 2017 new project wizard, there's currently no obvious way to create a Xamarin app that uses .Net Standard 2.0. Under Other Languages, Visual C#, there's Cross Platform App (Xamarin). Next, for UI Technology choose Xamarin.Forms and Code Sharing Strategy choose Portable Class Library (PCL). From there the following two links got me started converting the PCL project to .Net Standard:


Building

Building for the first time resulted in a bunch of errors similar to the following:
 obj\Debug\netstandard2.0\mobile_app.AssemblyInfo.cs(14,12,14,54): error CS0579: Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute  
 obj\Debug\netstandard2.0\mobile_app.AssemblyInfo.cs(15,12,15,60): error CS0579: Duplicate 'System.Reflection.AssemblyConfigurationAttribute' attribute  
 obj\Debug\netstandard2.0\mobile_app.AssemblyInfo.cs(16,12,16,58): error CS0579: Duplicate 'System.Reflection.AssemblyDescriptionAttribute' attribute  
To fix this, I need to remove Properties/AssemblyInfo.cs from the project.
 App.xaml.cs(6,7,6,14): error CS0246: The type or namespace name 'Xamarin' could not be found (are you missing a using directive or an assembly reference?)  
 MainPage.xaml.cs(6,7,6,14): error CS0246: The type or namespace name 'Xamarin' could not be found (are you missing a using directive or an assembly reference?)  
 App.xaml.cs(10,32,10,43): error CS0246: The type or namespace name 'Application' could not be found (are you missing a using directive or an assembly reference?)  
 MainPage.xaml.cs(10,37,10,48): error CS0246: The type or namespace name 'ContentPage' could not be found (are you missing a using directive or an assembly reference?)  
 App.xaml.cs(19,33,19,40): error CS0115: 'App.OnStart()': no suitable method found to override  
 App.xaml.cs(24,33,24,40): error CS0115: 'App.OnSleep()': no suitable method found to override  
 App.xaml.cs(29,33,29,41): error CS0115: 'App.OnResume()': no suitable method found to override  
For this, right-click VS project, Manage NuGet Packages, and add Xamarin.Forms.
 error : Project 'mobile_app\mobile_app\mobile_app\mobile_app.csproj' targets '.NETStandard,Version=v2.0'. It cannot be referenced by a project that targets 'UAP,Version=v10.0.10586'.  
I couldn't just build a UWP application with .NetStandard 2.0, it required upgrading Visual Studio 2017 to 15.4 and setting the Minimum Version of the UWP project to "Windows 10 Fall Creators Update" (may first need to set Target Version to the same).
 Could not load file or assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.  
 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'  
   at System.ModuleHandle.ResolveType(RuntimeModule module, Int32 typeToken, IntPtr* typeInstArgs, Int32 typeInstCount, IntPtr* methodInstArgs, Int32 methodInstCount, ObjectHandleOnStack type)  
   at System.ModuleHandle.ResolveTypeHandleInternal(RuntimeModule module, Int32 typeToken, RuntimeTypeHandle[] typeInstantiationContext, RuntimeTypeHandle[] methodInstantiationContext)  
   at System.Reflection.RuntimeModule.ResolveType(Int32 metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)  
   at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(CustomAttributeRecord caRecord, MetadataImport scope, Assembly& lastAptcaOkAssembly, RuntimeModule decoratedModule, MetadataToken decoratedToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, Object[] attributes, IList derivedAttributes, RuntimeType& attributeType, IRuntimeMethodInfo& ctor, Boolean& ctorHasParameters, Boolean& isVarArg)  
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes, Boolean isDecoratedTargetSecurityTransparent)  
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeAssembly assembly, RuntimeType caType)  
   at System.Reflection.RuntimeAssembly.GetCustomAttributes(Type attributeType, Boolean inherit)  
   at System.Attribute.GetCustomAttributes(Assembly element, Type attributeType, Boolean inherit)  
   at System.Attribute.GetCustomAttribute(Assembly element, Type attributeType, Boolean inherit)  
   at System.Reflection.CustomAttributeExtensions.GetCustomAttribute[T](Assembly element)  
   at Microsoft.Build.Tasks.ProcessResourceFiles.ReadAssemblyResources(String name, String outFileOrDir)  
Updating Microsoft.NETCore.UniversalWindowsPlatform package to the latest version on nuget solved this. There's also a warning about incompatible versions of Xamarin.Forms, but for that I just removed the reference to the old assembly from the UWP project.


Running

 DEP3321: To deploy this application, your deployment target should be running Windows Universal Runtime version 10.0.16299.0 or higher. You currently are running version 10.0.15063.674. Please update your OS, or change your deployment target to a device with the appropriate version.  
Here we're targeting Fall Creators Update but I wasn't running it yet. Previously I would have to enroll in Windows 10 Insider Preview, but as of today Fall Creators Update is officially released. Finally, I'm not sure if this is actually required for UWP apps, but it's needed for Android so I'll mention it here anyway. Xaml files need to be available at runtime, so right-click each and in Properties set "Build Action" to "Embedded resource".



2017/10/19

Migrating Orleans backend from Windows to Linux, AWS, and beyond

This is a draft of a post I composed in March, 2016. Only partially completed, but maybe it will be of some value to someone.

Our backend has been based on Microsoft Orleans (MS Research, GitHub) for some time. We're primarily running the entire backend- deployment and all- on Windows 7 mostly because:

  1. It's convenient since all developers are running Windows on their desktops
  2. It's easier for QA to test and operate playtests until everything is fully automated and we've got devops tools in place
  3. Our company is mostly a "Windows shop" anyway
It's been a long time coming, but we've finally gotten around to looking at running the backend on Linux. A few components, namely Zookeeper and MongoDB are fairly straight-forward since they're arguably intended to run on Linux. That leaves Orleans itself and, of course, our own game server.

Orleans should "in theory" work fine on top of Mono. However, our Orleans projects are a bit of a mess, so I thought I'd just try running the binaries produced by Visual Studio. And shockingly (see "...debug everywhere"), it mostly worked; we only ran into the following problems:

  • We use log4net and our App.config was specifying ColoredConsoleAppender which didn't work on Mono (switched to ConsoleAppender)
  • We spawn our game servers using Process.StartInfo.UseShellExecute = true and caused an error "xdg-open: unexpected option ..." (we set UseShellExecute to false on Linux- Windows seems to require it)
  • Our config file had a Windows "smell"
  • It refused to startup with the version of mono that ships with Ubuntu 14.04, but installing the latest, stable version fixed that
Next came getting our game server to compile and run on Linux. Luckily, our WAF-based build system should "just work" on Linux (in theory). Most of the compilation and runtime problems fell into just a few categories:
  • Hard-coded windows specific paths and commands
  • Paths that were incorrect with a case-sensitive filesystem
  • Asserts that were triggered by incomplete implementations

N.B.: At the time of writing we were using CryEngine 5.0. Since then the WAF buildchain has been retired in favor of cmake

Our initial round of deployment scripts where based on PowerShell 3.o and left a lot to be desired. PowerShell 3 (as opposed to 1 or 2) was chosen because it provided disconnected sessions allowing us to execute things on a remote machine and leave them running in the background without leaving a shell connected (or installing as a service). But, PowerShell 3 isn't installed by default on Windows 7 and then some other stuff is needed on remote machines to get remote commands working at all. Generally speaking, it was the overall idiosyncratic behaviour (Invoke-Command takes PSCredentials but old commands like "net use" don't- and there's apparently no modern replacement), and unfamiliar syntax (passing ArgumentList to Invoke-Command's ScriptBlock) that made the PowerShell prototype take longer than I expected. And there's still a few things that just don't work. Granted, I'm a completely new to PowerShell, and like all advanced tools there's a nontrivial learning curve. PowerShell is without a doubt leaps and bounds better than cmd.exe, and some aspects like getting auto-complete for commands and arguments in the ISE were welcome.

In a completely unfair comparison (given that I have non-trivial Linux experience), we were able to fully automate deployment to Ubuntu in under a day. The frustration of Invoke-Command/Credential/InDisconnectedSession was replaced with the relatively straightforward use of ssh and nohup. And, once you get password-less ssh and sudo setup, and embrace other commands like screen, working with multiple remote machines is a breeze.

All-in-all we were up and running on Ubuntu 14.04, rocking nightly builds in Jenkins, deploying to AWS, and doing playtests within a week. *opens a beer*