Subclassing C# classes.

Aug 15, 2013 at 5:15 AM
Hey,

I'm embedding IronScheme in a unity game. One of the main ways unity works is you have to create classes that subclass their MonoBehavior class, overload a few of the methods and then attach them to various game objects and the game engine uses them to call your code at the proper times and stuff.

I'm wondering how I can create a clr class in scheme that I'll then be able to instantiate and use with unity. I've been reading the forums here and saw that define-record-type might be the way to go. Can define-record-type have a parent that's a clr class? Is that the right way to create a class that subclasses an existing one that I can then modify the methods and fields on? I looked around a bit but can't seem to find any examples. A simple example would help immensely.

Anybody know how to do something like this?

Thanks!
~Stephen
Coordinator
Aug 15, 2013 at 9:32 AM
Hi

What you are looking for is not available directly in IronScheme. I have been looking for solution for this for a couple of years now, but due to the issue never really coming up, I have not made any progress on this.

The alternative way, is to create a 'stub' class in say C#.
class MyBehavior : MonoBehavior
{
  static Callable foo;

  public override int Foo(string bar)
  {
    return (int) foo.Call(this, bar);
  }
}
Depending on your needs, you may not want to make the implementation static of the Callable field.

Now in Scheme you set it up:
(define (my-foo this bar)
  (string-length bar))

(clr-static-field-set! MyBehavior foo my-foo)
Usage:
(define behave (clr-new MyBehavior))

; do what you need to do with Unity to make behave ie
(register behave)
my-foo will now be called.

Hope this helps.

Cheers

leppie
Aug 15, 2013 at 7:36 PM
Cool, thanks.

That'll suit some of my needs though it's a bit hackyer than I'd like. It also limits a lot of the features of unity that manipulate things based on the type. Is there anything on the roadmap to support creating real .net classes that could even be instantiated from c#? I've done a lot of clojure work and the way their protocols and types work are a very nice way to do interop like this. I'd think coming up with a schemey version of that would be quite useful.

Thanks again, this lets me make some progress.
Coordinator
Aug 15, 2013 at 7:43 PM
I will give it some more thought and perhaps I can come up with a decent solution.
Aug 16, 2013 at 9:36 PM
Ok I've been playing around a bit more and am running into problems.
Firstly I'm not sure what a Callable is (at least my intellesense in unity doesn't), I tried making a delegate type but I'm getting an error.
public delegate void VoidFunction();

public class StubBehavior : MonoBehaviour {
    
    public VoidFunction updateFn;

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        if (updateFn != null) {
            updateFn();
        }
    }
}
Then when I try to make a function and set it to that updateFn var like so
(define (my-update)
  (clr-static-call Debug Log ""I'm Attached!!!!""))
(clr-static-field-set! StubBehavior ""updateFn"" my-update)");
I get an error
SchemeException: Cannot cast from source type to destination type.
Also I need to be able to make multiple classes on the fly so I wont be able to make a stub class. Is there some way in c# to make a dynamic class at runtime that I could call and then mess with it from scheme?
Aug 19, 2013 at 5:47 PM
I figured out what a Callable was but even with my field as a Callable I still can't assign to it. Get the cannot cast error.
using System;
using UnityEngine;
using System.Collections;
using IronScheme.Runtime;

public class StubBehavior : MonoBehaviour {
    
    static Callable updateFn;

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        if (updateFn != null) {
            updateFn.Call();
        }
    }
}
Then the scheme code I eval at runtime.
(clr-using StubBehavior)
(define (my-update)
  (clr-static-call Debug Log ""I'm Attached!!!!""))
(clr-static-field-set! StubBehavior ""updateFn"" my-update)"
SchemeException: Cannot cast from source type to destination type.
Coordinator
Aug 19, 2013 at 6:52 PM
Hi

clr-using is for namespaces. clr-reference is used for loading an assembly, but you will not need it if the assembly is loaded already.

Also, updateFn should be public. I am not sure why it does not error on that.

I will try recreate it scenario to see what is wrong.

Cheers

leppie
Coordinator
Aug 19, 2013 at 7:03 PM
Edited Aug 19, 2013 at 7:17 PM
Here is a working example:
using System;
using IronScheme;
using IronScheme.Runtime;

public class StubBehavior
{
  public static Callable updateFn;

  // Use this for initialization
  void Start()
  {

  }

  // Update is called once per frame
  public void Update()
  {
    if (updateFn != null)
    {
      updateFn.Call();
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
    @"
(import (ironscheme clr))

(define (my-update)
  (clr-static-call Console WriteLine ""I'm Attached!!!!""))

(clr-static-field-set! StubBehavior updateFn my-update)".Eval();

    var sb = new StubBehavior();

    sb.Update(); // prints I'm Attached!!!!

    Console.ReadLine();
  }
}
Aug 20, 2013 at 2:42 AM
Ok cool, that helped. I think I'm really close. The only difference in what I have to do is the way I attach the class to an object.

In my scheme I'm evaluating I have this.
Debug.Log(IronScheme.RuntimeExtensions.Eval(@"
(import (ironscheme clr))
(clr-using UnityEngine)

(define (my-update)
  (clr-static-call Debug Log ""I'm Attached!!!!""))

(clr-static-field-set! Stubby FN my-update)
(define obj (clr-static-call GameObject Find ""Test""))
(clr-call GameObject obj AddComponent ""StubBehavior"")"));
The extension methods don't work through unity for some reason but that should be fine. On the last call there I'm getting an error trying to call the AddComponent method on obj. Find is a static method on the GameObject class, it returns an instance of GameObject and then AddComponent is a method on the object I am trying to call. The error I'm getting is this.
&who: #f
&message: "member could not be resolved on type: GameObject"
&syntax:
  form: #f
  subform: #f
It isn't really a very descriptive message. Is there something I have to do to define obj as a GameObject from scheme?
Coordinator
Aug 20, 2013 at 5:45 AM
Edited Aug 20, 2013 at 5:45 AM
(clr-call GameObject obj AddComponent "StubBehavior")
clr-call is Type, Method, instance, args ....

So the correct way is:
(clr-call GameObject AddComponent obj "StubBehavior")
Will have to check why it did not say obj is not a member.

BTW what version of IronScheme are you using?
Sep 3, 2013 at 1:48 AM
Hey, sort of abandoned this for awhile but I'm playing with it again.
First of all, it's awesome, thanks for the help! This works, I can use a stub class and set the function that gets called to whatever I want it to be.

Here are some other things I'm thinking about as I play with it more.
1) A way to make .net classes that extend MonoBehavior at runtime will still be nicer because I can fully leverage the way unity wants to interact with game objects.

2) Does Ironscheme have any remote repl functionality built in? I'd like to set up a repl from inside my game and then connect to it with emacs and manipulate things.
Sep 5, 2013 at 1:24 AM
Ah! I ran into the same problem the other unity thread had. The errors about Console not existing. It works fine in the unity game maker tool but in a standalone build it errors. It must be because the game maker tool runs on a more recent version of mono (or a full version of mono?) and the game runtime is their restricted version. That's a bummer.