How to get which version of the .net framework SDK is running

In the bad old world before .net core/v4 I was struggling to figure out how to return the version of the .net framework code was using. This is my best effort so far. NB Assumes you’re references System.Core;

var references = System.Reflection.Assembly.GetExecutingAssembly().GetReferencedAssemblies();
var core = references.FirstOrDefault(r => r.Name == "System.Core");
var sdkVersion = core.Version.ToString();

Auto Convert Roads Types from Real World Terrain

Real World Terrain is a great Unity Asset to kick start a Unity Terrain based upon a real life location. One of the helpful features is that it will can also be used in conjunction with EasyRoads3D to generate a road network based on actual road data. However, you might find yourself in a situation were you’ve requested that all roads types (highways to dirt paths) are included but you end up with everything looking like 6 metre asphalt roads. If you’re in this situation then you can use the following script (or tweaked to your needs) to convert them to something more useful. NB It also sets the width of the road to that specified in the meta data.

Instructions:

  1. Make sure you have a road type you want to use in EasyRoads3D, e.g. Track
  2. Add the script to your ‘Road Objects’ game object
  3. Set the ‘Not From Road Surface’ to surface that you DO NOT want to be effected by the change, e.g. asphalt, i.e. please don’t change anything that has a surface of asphalt
  4. Set the ”To Road Type’ to the name of EasyRoads3D type your want, e.g. Track
  5. Check the ‘Should Run’ field
using System;
using System.Linq;
using EasyRoads3Dv3;
using InfinityCode.RealWorldTerrain.OSM;
using UnityEngine;

namespace CBC
{
    [ExecuteInEditMode]
    public class EasyRoads3DTypeMapper : MonoBehaviour
    {
        [SerializeField]
        string fromNotRoadSurface;
        
        [SerializeField]
        string toRoadType;

        [SerializeField]
        bool shouldRun = false;

        private void OnValidate()
        {
            if (shouldRun)
            {
                int roadsChanged = 0;
                try
                {
                    var roadNetwork = new ERRoadNetwork();
                    if (roadNetwork == null)
                    {
                        Debug.LogError("Unknown road network");
                        shouldRun = false;
                        return;
                    }

                    var erModularBase = GetComponentInParent();
                    var roadTypes = erModularBase.GetRoadTypes();
                    // DisplayRoadTypes(roadTypes);

                    var requestedRoadType = roadTypes.FirstOrDefault(rt => rt.roadTypeName.Equals(toRoadType, System.StringComparison.InvariantCultureIgnoreCase));
                    if (requestedRoadType == null)
                    {
                        Debug.LogError("Unknown road type " + toRoadType);
                        shouldRun = false;
                        return;
                    }

                    var realWorldTerrainOSMMetas = GetComponentsInChildren();
                    foreach (var rwtRoad in realWorldTerrainOSMMetas)
                    {
                        var erRoad = rwtRoad.GetComponent();
                        RealWorldTerrainOSMMetaTag surface = GetMetaInfo(rwtRoad, "surface");

                        var isTargetSurfaceForChange = surface == null || (surface != null && !surface.info.Equals(fromNotRoadSurface, System.StringComparison.InvariantCultureIgnoreCase));

                        var roadType = erRoad.GetRoadType(roadTypes);
                        if (isTargetSurfaceForChange && roadType != requestedRoadType)
                        {
                            var estWidthInfo = GetMetaInfo(rwtRoad, "est_width");
                            var estWidth = estWidthInfo?.info ?? "0";
                            var requestedWidth = (float)Convert.ToDouble(estWidth);

                            print(roadType?.roadTypeName ?? "No road type set");
                            print($"{surface?.info ?? "(surface not set)"} != {fromNotRoadSurface}");



                            print($"Changing {erRoad.name} {roadType?.roadTypeName ?? "not set"} to {requestedRoadType.roadTypeName}");
                            print($"Current width: {erRoad.GetRoadWidth()} Requested Width {estWidth} ");

                            var road = erRoad.road;
                            if (road == null)
                            {
                                road = roadNetwork.GetRoadByName(erRoad.name);
                                if (road == null)
                                {
                                    Debug.LogError("Road name not found in network");
                                }
                            }

                            if (road == null)
                            {
                                Debug.LogError("Road is missing, cannot change any details");
                            }
                            else
                            {
                                road.SetRoadType(requestedRoadType);
                                if (requestedWidth > 0f)
                                {
                                    road.SetWidth(requestedWidth);
                                }
                                roadsChanged++;
                            }
                        }
                    }
                }
                finally
                {
                    print($"Changed {roadsChanged} roads in this run");
                    shouldRun = false;
                }
            }
        }

        private void DisplayRoadTypes(ERRoadType[] roadTypes)
        {
            for (int x = 0; x  m.title == key);
        }

        private static void DisplayMetaInfo(RealWorldTerrainOSMMeta rwtRoad)
        {
            print($"metaInfo: {rwtRoad.metaInfo.Length}");
            for (int x = 0; x < rwtRoad.metaInfo.Length; x++)
            {
                print($"{rwtRoad.metaInfo[x].title} {rwtRoad.metaInfo[x].info}");
            }
        }
    }
}

Convert Vegetation Studio Pro’s Vegetation Mask to Biome Mask

Vegetation Masks (VM) can be a convenient way to allocate different vegetation types to an area. However, they don’t really play nicely with other Vegetation Studio features such as Vegetation Mask Lines (ML). The problem is that whatever vegetation you include in a VM will always override any attempts to use an ML to exclude vegetation. What does that all really mean? If you add a road to a scene then naturally you don’t want a huge tree in the middle of it. So you assign the road an ML and exclude all trees, simple. But if the trees are there because the road overlaps a VM then you’ll still have a forest in the middle of your road.

The answer to this is to use Biome Masks (BM). A BM fulfills a similar role, you can assign it different vegetation (along with lots of other options). A ML will correctly remove any vegetation from a BM and therefore you can have tree-free roads.

“Oh no, but I have created lots of VMs”
You may have a scene that you’ve painstakingly created using VMs, or perhaps you’ve used the Real World Terrain asset which generates VMs from the underlying texture (nice). If you have, then you can use the following script to convert VMs to BMs.

Instructions:

  1. Create your Biome in Vegetation Studio (Pro), remember the Biome type
  2. Create an empty game object in your scene for every type of Vegetation Mask; e.g. Grasses, Trees. This will be the Biome’s parent object
  3. Assign the script to the parent game object containing your VMs
  4. Select the Biome type from (1)
  5. Enter the “Starts With Mask” to find all the VMs that start with that name. So if you have VMs called “Grass 12344”, “Grass 54323”, etc, you would enter “Grass”
  6. Assign the associated empty game object from (2), i.e. drag that node into “Biome Parent”
  7. Repeat 3-6 for each type of conversation you want. E.g. another one with a “Starts With Mask” of “Tree”
  8. To run a conversion select the “Should Run” checkbox

Once run you should find the converted Biome Masks under the appropriate parent objects. The original Vegetation Masks are still there but have been disabled.

using System.Collections;
using System.Collections.Generic;
using AwesomeTechnologies.VegetationSystem;
using AwesomeTechnologies.VegetationSystem.Biomes;
using UnityEngine;

namespace CBC
{
    [ExecuteInEditMode]
    public class VegToBiomeMask : MonoBehaviour
    {
        [SerializeField]
        Transform biomeParent;

        [SerializeField]
        BiomeType biomeType;

        [SerializeField]
        string startWithMask;

        [SerializeField]
        bool shouldRun = false;

        void Update()
        {
            if (shouldRun)
            {
                var defaultGameObject = new GameObject();
                shouldRun = false;
                var childMasks = gameObject.GetComponentsInChildren();
                foreach(var childMask in childMasks)
                {
                    if (childMask.enabled && childMask.name.StartsWith(startWithMask))
                    {
                        var nodes = childMask.Nodes;
                        var biomeMaskGameObject = Instantiate(defaultGameObject, biomeParent.transform);
                        biomeMaskGameObject.transform.position = new Vector3(childMask.transform.position.x, childMask.transform.position.y, childMask.transform.position.z);
                        biomeMaskGameObject.name = "B" + childMask.name;
                        var biomeMaskArea = biomeMaskGameObject.AddComponent();
                        biomeMaskArea.ClearNodes();
                        foreach (var node in nodes)
                        {
                            biomeMaskArea.AddNode(new Vector3(node.Position.x, node.Position.y, node.Position.z));
                        }

                        // for some strange reason you have to reassign the positions again otherwise they all have an incorrect offset??
                        for (int x = 0; x < nodes.Count; x++)
                        {
                            var node = nodes[x];
                            var bNode = biomeMaskArea.Nodes[x];
                            bNode.Position = new Vector3(node.Position.x, node.Position.y, node.Position.z);
                        }

                        biomeMaskArea.BiomeType = biomeType;
                        childMask.enabled = false;
                    }
                }
            }
        }
    }
}

Azure Durable Function gotcha

I’ve been creating a particular sequential workflow with Durable Functions and I hit a strange problem where it would get to about step 3 and it just would not proceed past it. I couldn’t see any obvious error. The issue was that the state being stored from that call was larger than the maximum size for an Azure Table Row (in this case it was a return value). This is important because that’s where Durable Functions stores the state. So be careful out there, keep your state small or save it yourself (e.g. to Blob)

Parsing console args

It’s typical for a console app to be driven by args. Often these can be a complex set, e.g. MyApp.exe title:=bob maxSize:=300 outputFile:=results.txt

I keep re-using the following code, so I thought I’d share it;


var commands = CommandsFromArgs(args);
if (commands.ContainsKey("maxSize))
   var maxSize = commands["maxSize"];

/// <summary>
/// Commands from arguments.
/// </summary>
/// The arguments.
/// dictionary of commands
private static Dictionary<string,string> CommandsFromArgs(string[] args)
{
    var commands = new Dictionary();
    var splitter = new string[] { ":=" };
    foreach (var arg in args)
    {
        if (arg.Contains(":="))
        {
            var pair = arg.Split(splitter, StringSplitOptions.None);
            if (pair.Length == 2)
            {
                commands.Add(pair[0], pair[1]);
            }
        }
    }

    return commands;
}

How to Use Visual Studio Performance Analyzers against Unit Tests

Having a nice set of automated tests in your project has lots of well publicized advantages. However, if you want to use the tests to help analyze a performance issue you get stuck with a simple problem, how do I run the test and run the Performance Analyzer?

The trick is that the Visual Studio Test project is itself a console application, but it is a sort of headless console application. This isn’t helpful has Performance Analyzer wants you to set a start-up project and without being able to write some code to invoke your tests you’re stuck. Normally a console application will have Project.cs file but test projects automatically build this behind the scenes. This means that if you simply drop a new Project.cs file into your test project it won’t build because you’ll get errors ‘Program has more than one entry point defined’. To allow your Program.cs file to be used you need to edit the project and add the following line after the TargetFramework property item;

<GenerateProgramFile>false</GenerateProgramFile>

Now your test project will have an easy to use Console/Main/Program.cs that you can code to invoke your tests. All you have to do now is point Performance Analyzer at it and press Start.

JsonReaderException: Error reading JArray using Adaptive Cards with Bot Framework

AdpativeCards should work well in the Bot. You should be able to produce great looking cards and also be able to respond to user input. In the latter case you are likely to run into a nasty little problem with how the serialization works with the Bot Framework and Adaptive Cards. If you’re struggling then there is a cheeky workaround;
The links starts at ~11mins into the whole tutorial https://youtu.be/2Hy9m5MvD4o?t=673

A Star Wars aside – Niggles far far away

I was very young when I first saw the trailer for Star Wars, with the TIE fighters attacking the Falcon. It’s fair to say I was hooked. Obviously the appeal to a 4-5 year old is a different than I have for some of the later films but I still connect with that early joy and I often rate a film by how much of an urge I have to jump off a sofa and re-enact some part of the story. But re-watching The Last Jedi it occurred to me that over the years there have been some irritating niggles that I hope future film makers can take note of.

  1. Stormtroopers can’t hit a barn door. There are plenty of times we see or hear about how precise and well trained the Stormtroopers are. But when it comes to shooting our heroes they can’t hit the side of barn door. Yes I know that’s true of lots of these shows; cowboys, A-Team, James Bond, etc. etc but it still annoys me
  2. R2D2 Rocket ship. You can’t assign great features or attributes to a character in the story timeline only for them not to exist later on. Dumb, stupid, dumb
  3. Slick CGI overly perfect ships. I suspect this will be an on-going theme, just like (2) you need to at least have a nod towards the timeline. Okay, so I can just about believe that in a time of peace the vehicles might all be in pristine condition but it just doesn’t work
  4. Abide by the story don’t spoil the mystery. Midi-Chlorians. No. You create a mystical aspect, leave it alone. We don’t need every last thing explained, allow us something to think about. Thankfully the recent set of films have conveniently ignored that tripe
  5. Vehicles with a purpose. This is a minor issue to me but a bit like (1) it just annoys me, always has. AT-ATs. What is the point of them? You’ve got vehicles that are happy in space but apparently it’s ‘too cold’ to defy gravity on Hoth? Riiiggghhht. Anyway, they are fun so I let this one go 😊
  6. Bombing run against the dreadnought. In space…bombs fall under gravity. Okay. I can just about swallow this for the drama, but only just
  7. Is it bird? It is a plane? No, it’s super Leia. See (4). Dreadful bit of The Last Jedi, awful, ruins it for me. Utter nonsense that serves no purpose to the narrative apart from saying, “Yes, I don’t care about the story universe. I’m gonna bust some holes”. Yeah, well done. Oh I forgot to ask how she gets back through the sealed door with a gaping hole into space. Yes, better not to ask. Just erase that whole section from memory
  8. The slow car chase in space – more story nonsense. Why wouldn’t the Imperial, sorry First Order, fleet just jump and surround them. Silly, but okay, drama – I get it
  9. The hard-disk library in Rogue One – where technology and story line clash. Okay, again for the drama, but come on, this was supposed to be a more adult film. It’s equivalent to all these rubbish shows were people just view contacts on a closed phone or desktop without ever needing a password. Yes it’s not real, but my suspension of disbelief can only be stretched so far
  10. Han & Chewie meet. Now, we know Chewie is strong, at least we hear a lot about. But the human body (I’m assuming Corellian equals Human) is pretty fragile. They’d be a puddle of skin and bones after that scene
  11. Flying reflexes. I love the speeder bikes on Endor. Dodging asteroids in Solo was fun but come on. You can just about forgive this in the context of mystical Jedi types, but those reflexes are ridiculous
  12. Hyper space jump attack. Oh dear, if this works then it would be a constant tactic (a problem that BSG also suffers from). Nope, no, nope, nah.
  13. Alliance ship-by-letter. Ok we get X-Wing, it makes sense. But then it all goes wrong. What is happening in the ship-building yards? Shall we build a new fighter based on a need? Nah, that’s too complicated, lets just just with a letter from an alphabet we don’t even use and create ships that loosely share the same shape as a letter. A, B, U, V, X, Y. Yes, that’s plausible 😉

There, that’s got that off my chest. By all means suspend my disbelief but keep the level consistent. Oh and if someone does a directors cut of The Last Jedi where Leia gets injured in the attack rather than coming from Krypton then please let me know.

Gotcha with Bot Framework AutoSaveStateMiddleware and “long” flows

The AutoSaveStateMiddleware is a great addition to a Bot project. On every turn it can save the BotState objects you configure it to look after. Takes a way a lot of noise and accidentally forgetting to call save yourself. However, this is a little gotcha with this mechanism. Consider a classic Pizza ordering scenario;

  • USER – What pizzas do you have?
  • BOT – We have Vegetarian, Pepperoni, Ham & Cheese, Ham & Mushroom

From a Bot Framework perspective you might implement that as a series of Waterfall steps – potentially using a 3rd party service, i.e. potentially unreliable – slow;

  • Gather Available Ingredients For Location Step, NextAsync
  • Gather Available Pizza Recipes for Available Ingredients Step, NextAsync
  • Show Available Pizza Step

So far so good. Now, let’s say that before we started this Waterfall off we provided the user with a ‘See our drinks menu’ hero card button. What we expect to happen is that the drinks button will be shown before the potentially longer running food menu is processed and shown to the user. But what happens if the user presses that drinks button before the food menu has run through its steps and displayed its results? Well, something not so pleasant is the answer. Since the AutoSaveState will fire on the Turn, it won’t have fired until it prompts the user for their pizza choice. Therefore, even though the Drink button is in the same overall flow, the same activity, when the message is received by the bot it will NOT have an Active Dialog set in its context. This means that the message will NOT be passed onto what we think is our active dialog.

What solutions are there?

  • You can manually call the Save State, but it’s sort of a pain because that’s why we’re using the AutoSaveState
  • Simply ignore this issue. You may lose messages but the user will just try again
  • Use a base class for your dialogs that overrides NextAsync and saves the Dialog state