diff --git a/docs/unity/bindings.md b/docs/unity/bindings.md new file mode 100644 index 00000000..7a77841e --- /dev/null +++ b/docs/unity/bindings.md @@ -0,0 +1,189 @@ +--- +label: Bindings +icon: dot +order: 10 +--- + +Bindings are methods and variables that are defined in C# and can be accessed from Python. +We provide two types of bindings: static bindings and dynamic bindings. + +## Static Bindings + +Static bindings wrap a C# class or struct and expose its methods and variables to Python. +This is the most common way to define bindings. +Static bindings are initialized at compile time. + +### Manual Static Bindings + +Manual static bindings directly create a Python equivalent of `def f(a, b, *args)` in C#. +To use it, you need to create a class that inherits from `PyTypeObject`. +And implement some abstract methods to specify the name and type of the Python type. +For example, to make `UnityEngine.Vector2` available in Python, you can write a `PyVector2Type` +class like the following. + +```csharp +public class PyVector2Type: PyTypeObject{ + // The name of the type in Python + public override string name => "Vector2"; + + // The corresponding C# type + public override System.Type type => typeof(Vector2); +} +``` + +Next, you need to define each method and variable to be exposed to Python, +by using `[PythonBinding]` attribute. + +!!! +We assume that you have necessary knowledge about +[Python's data model](https://docs.python.org/3/reference/datamodel.html). +Such as magic methods, `__new__`, `__init__`, `__add__` and so on. +Otherwise, you may have trouble understanding the following code. +!!! + +Let's define a magic method `__add__`, it is used to implement the `+` operator in Python. +With `__add__`, `Vector2` object in Python can be added with another `Vector2` object. + +```csharp +public class PyVector2Type: PyTypeObject{ + public override string name => "Vector2"; + public override System.Type type => typeof(Vector2); + + [PythonBinding] + public static object __add__(VM vm, Vector2 self, object other){ + // If the other object is not a Vector2, return NotImplemented + if(!(other is Vector2)) return VM.NotImplemented; + // Otherwise, return the result of addition + return self + (Vector2)other; + } +} +``` + +This is easy to understand, right? +Let's see another example, `__mul__`, it is used to implement the `*` operator in Python. +`Vector2` object in C# can be multiplied with a `float` object in Python. +The following code shows this usage. + +```csharp +Vector2 a = new Vector2(1, 2); +Vector2 b = a * 2.0f; +Vector2 c = 2.0f * a; +``` + +As you can see, things are slightly different from `__add__`. +Because the `float` operand can be on the left or right side of the `*` operator. +In this case, you need to define `__mul__` and `__rmul__` at the same time. + +```csharp +public class PyVector2Type: PyTypeObject{ + public override string name => "Vector2"; + public override System.Type type => typeof(Vector2); + + // ... + + [PythonBinding] + public static object __mul__(VM vm, Vector2 self, object other){ + if(!(other is float)) return VM.NotImplemented; + return self * (float)other; + } + + [PythonBinding] + public static object __rmul__(VM vm, Vector2 self, object other){ + if(!(other is float)) return VM.NotImplemented; + return self * (float)other; + } +} +``` + + +Finally, let's implement the constructor of `Vector2`. +`__new__` magic method must be defined. + +```csharp +public class PyVector2Type: PyTypeObject{ + public override string name => "Vector2"; + public override System.Type type => typeof(Vector2); + + [PythonBinding] + public static object __new__(VM vm, PyTypeObject cls, params object[] args){ + if(args.Length == 0) return new Vector2(); + if(args.Length == 2){ + float x = vm.PyCast(args[0]); + float y = vm.PyCast(args[1]); + return new Vector2(x, y); + } + vm.TypeError("Vector2.__new__ takes 0 or 2 arguments"); + return null; + } +} +``` + +Here we use `params object[] args` to tell the bindings that the constructor can take any number of arguments. +It is equivalent to `def __new__(cls, *args)` in Python. +Note that Python does not support method overloading. +So we manually check the number of arguments and their types to determine which constructor to call. + +For fields, we can form a Python property by defining a getter and a setter. +By using `[PythonBinding(BindingType.Getter)]` and `[PythonBinding(BindingType.Setter)]` attributes. + +!!! +However, this has certain limitations for value types. Because `Vector2` is a struct, it is passed by value. +So our setter will not be able to modify the original `Vector2` object. +!!! + +```csharp +public class PyVector2Type: PyTypeObject{ + public override string name => "Vector2"; + public override System.Type type => typeof(Vector2); + + [PythonBinding(BindingType.Getter)] + public static object x(VM vm, Vector2 self) => self.x; + + [PythonBinding(BindingType.Setter)] + public static void x(VM vm, Vector2 self, object value) => self.x = vm.PyCast(value); + + [PythonBinding(BindingType.Getter)] + public static object y(VM vm, Vector2 self) => self.y; + + [PythonBinding(BindingType.Setter)] + public static void y(VM vm, Vector2 self, object value) => self.y = vm.PyCast(value); +} +``` + +Once you have done all the above, you must register the type to the VM. +And set the returned object into a module. +Here we set it into `builtins` module, so that it can be accessed from anywhere. + +```csharp +var type = vm.RegisterType(new PyVector2Type()); +vm.builtins.attr["Vector2"] = type; +``` + +To summarize, manual static bindings provide detailed control for exposing a C# class to Python. +You decide which methods and variables to expose, and how to expose them. +This is our recommended way to define bindings. Also it is the most performant way. + +### Automatic Static Bindings + +Automatic static bindings use C# reflection to automatically generate bindings for a C# class. +It is convenient for testing and prototyping, but it is slow and unsafe since the user can access any member of the class. + +```csharp +var type = vm.RegisterAutoType(); +vm.builtins.attr["Vector2"] = type; +``` + +That's all you need to do. The `RegisterAutoType` method will automatically generate bindings for `Vector2`. + + +## Dynamic Bindings + +Dynamic bindings allows you to add a single C# lambda function to an object at runtime. + +```csharp +delegate object NativeFuncC(VM vm, object[] args); +``` + ++ `CSharpLambda BindFunc(PyObject obj, string name, int argc, NativeFuncC f)` + +You can use `BindFunc` to achieve this. diff --git a/docs/unity/index.yml b/docs/unity/index.yml new file mode 100644 index 00000000..a0024f8e --- /dev/null +++ b/docs/unity/index.yml @@ -0,0 +1,3 @@ +label: Unity Plugin +icon: code +order: 0 \ No newline at end of file diff --git a/docs/unity/introduction.md b/docs/unity/introduction.md new file mode 100644 index 00000000..965d680b --- /dev/null +++ b/docs/unity/introduction.md @@ -0,0 +1,19 @@ +--- +label: Introduction +icon: dot +order: 30 +--- + +# Welcome to PocketPyUnity + +PocketPyUnity is a C# plugin that allows you to do Python scripting in [Unity](https://unity.com/). +It provides a sandboxed Python environment, adding dynamic capabilities to your game, +which can be used for dynamic game logic, modding, hot fixing, and more. + +The virtual machine is written in **pure C#**, +which means you can fully control the internal state of the Python interpreter. + +!!! +PocketPyUnity is designed for game scripting, not for scientific computing. +You cannot use it to run NumPy, OpenCV, or any other CPython extension modules. +!!! \ No newline at end of file diff --git a/docs/unity/index.md b/docs/unity/vm.md similarity index 87% rename from docs/unity/index.md rename to docs/unity/vm.md index 25706280..6d7d5da6 100644 --- a/docs/unity/index.md +++ b/docs/unity/vm.md @@ -1,25 +1,9 @@ --- -label: Unity Plugin -icon: code -order: 0 +label: Virtual Machine +icon: dot +order: 20 --- -# Welcome to PocketPyUnity - -PocketPyUnity is a C# plugin that allows you to do Python scripting in [Unity](https://unity.com/). -It provides a sandboxed Python environment, adding dynamic capabilities to your game, -which can be used for dynamic game logic, modding, hot fixing, and more. - -The virtual machine is written in **pure C#**, -which means you can fully control the internal state of the Python interpreter. - -!!! -PocketPyUnity is designed for game scripting, not for scientific computing. -You cannot use it to run NumPy, OpenCV, or any other CPython extension modules. -!!! - -## Features - The `VM` class provides a sandboxed Python environment and a set of APIs for interacting with it. ### Construction @@ -144,12 +128,4 @@ The `VM` class provides a sandboxed Python environment and a set of APIs for int + `bool debug = false` A flag that controls whether to print debug messages to Unity console. - You can set it to `true` to enable debug messages, or `false` to disable them. - -## Bindings - -1 - -## Examples - -1 \ No newline at end of file + You can set it to `true` to enable debug messages, or `false` to disable them. \ No newline at end of file