This commit is contained in:
blueloveTH 2024-05-05 12:10:23 +08:00
parent 1bda712825
commit f7b0abff5a
7 changed files with 2 additions and 622 deletions

View File

@ -1,185 +0,0 @@
---
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 CSType => 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 CSType => typeof(Vector2);
[PythonBinding]
public object __add__(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.
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 CSType => typeof(Vector2);
// ...
[PythonBinding]
public object __mul__(Vector2 self, object other){
if(!(other is float)) return VM.NotImplemented;
return self * (float)other;
}
[PythonBinding]
public object __rmul__(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 CSType => typeof(Vector2);
[PythonBinding]
public object __new__(PyTypeObject cls, params object[] args){
if(args.Length == 0) return new Vector2();
if(args.Length == 2){
float x = vm.PyCast<float>(args[0]);
float y = vm.PyCast<float>(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 CSType => typeof(Vector2);
[PythonBinding(BindingType.Getter)]
public object x(Vector2 self) => self.x;
[PythonBinding(BindingType.Setter)]
public void x(Vector2 self, object value) => self.x = vm.PyCast<float>(value);
[PythonBinding(BindingType.Getter)]
public object y(Vector2 self) => self.y;
[PythonBinding(BindingType.Setter)]
public void y(Vector2 self, object value) => self.y = vm.PyCast<float>(value);
}
```
Once you have done all the above, you must register the type to the VM.
Here we set it into `builtins` module, so that it can be accessed from anywhere.
```csharp
vm.RegisterType(new PyVector2Type(), vm.builtins);
```
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
vm.RegisterAutoType<Vector2>(vm.builtins);
```
That's all you need to do. The `RegisterAutoType<T>` method will automatically generate bindings for `Vector2`.
## Dynamic Bindings
Dynamic bindings allow 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)`
It is similar to `bind_func` in [C++ API](../bindings/).

View File

@ -1,32 +0,0 @@
---
label: Python console
icon: dot
order: 5
---
You can open the Python console in Unity by clicking the `Window/Python Console` menu item.
By default, the console creates a unmodified `VM` instance to execute your code.
You may want to provide an enhanced `VM` instance for the console in Unity Editor.
For example, adding some class bindings in `UnityEngine` namespace.
To do this, you need to create a class derived from `VM` and put it in `Assets/Editor/` folder.
By adding `[EditorVM]` attribute to the class,
the console will use it instead of the default `VM` instance.
```csharp
using UnityEngine;
using PocketPython;
[EditorVM] // this attribute is required
public class EnhancedVM: VM{
public EnhancedVM() {
RegisterAutoType<GameObject>(builtins);
RegisterAutoType<Transform>(builtins);
RegisterAutoType<Vector2>(builtins);
RegisterAutoType<Vector3>(builtins);
// ...
}
}
```

View File

@ -1,166 +0,0 @@
---
label: Examples
icon: dot
order: 4
---
See `Assets/PocketPython/Examples` after you import the plugin.
### Primes Example
```csharp
using UnityEngine;
namespace PocketPython
{
/// <summary>
/// Example of using PocketPython to find prime numbers.
/// </summary>
public class PrimesExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
var vm = new VM();
const string source = @"
def is_prime(x):
if x < 2:
return False
for i in range(2, x):
if x % i == 0:
return False
return True
primes = [i for i in range(2, 20) if is_prime(i)]
print(primes)
";
CodeObject code = vm.Compile(source, "main.py", CompileMode.EXEC_MODE);
vm.Exec(code); // [2, 3, 5, 7, 11, 13, 17, 19]
}
}
}
```
### Vector2 Example
```csharp
using UnityEngine;
namespace PocketPython
{
/// <summary>
/// Example of making UnityEngine.Vector2 available to Python.
/// </summary>
public class Vector2Example : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
var vm = new VM();
// register UnityEngine.Vector2 type into the builtins module
vm.RegisterAutoType<Vector2>(vm.builtins);
vm.Exec("print(Vector2)", "main.py"); // <class 'Vector2'>
vm.Exec("v = Vector2(1, 2)", "main.py");
vm.Exec("print(v)", "main.py"); // (1.0, 2.0)
vm.Exec("print(v.x)", "main.py"); // 1.0
vm.Exec("print(v.y)", "main.py"); // 2.0
vm.Exec("print(v.magnitude)", "main.py"); // 2.236068
vm.Exec("print(v.normalized)", "main.py"); // (0.4472136, 0.8944272)
vm.Exec("print(Vector2.Dot(v, v))", "main.py"); // 5.0
vm.Exec("print(Vector2.get_up())", "main.py"); // (0.0, 1.0)
Vector2 v = (Vector2)vm.Eval("Vector2(3, 4) + v");
Debug.Log(v); // (4.0, 6.0)
}
}
}
```
### MyClass Example
```csharp
using UnityEngine;
using System;
namespace PocketPython
{
public class MyClass
{
public string title;
public string msg;
public void Print()
{
Debug.Log(title + ": " + msg);
}
}
public class PyMyclassType : PyTypeObject
{
public override string Name => "my_class";
public override Type CSType => typeof(MyClass);
[PythonBinding]
public object __new__(PyTypeObject cls)
{
return new MyClass();
}
[PythonBinding(BindingType.Getter)]
public string title(MyClass value) => value.title;
[PythonBinding(BindingType.Getter)]
public string msg(MyClass value) => value.msg;
[PythonBinding(BindingType.Setter)]
public void title(MyClass value, string title) => value.title = title;
[PythonBinding(BindingType.Setter)]
public void msg(MyClass value, string msg) => value.msg = msg;
[PythonBinding]
public void print(MyClass value) => value.Print();
}
/// <summary>
/// Example of binding a custom C# class to Python.
/// </summary>
public class MyClassExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
var vm = new VM();
// register MyClass type into the builtins module
vm.RegisterType(new PyMyclassType(), vm.builtins);
vm.Exec("print(my_class)", "main.py"); // <class 'my_class'>
vm.Exec("c = my_class()", "main.py");
vm.Exec("c.title = 'Greeting'", "main.py");
vm.Exec("c.msg = 'Hello, world!'", "main.py");
string title = vm.Eval("c.title").ToString();
string msg = vm.Eval("c.msg").ToString();
Debug.Log(title + ": " + msg); // Greeting: Hello, world!
vm.Exec("c.print()", "main.py"); // Greeting: Hello, world!
}
}
}
```

View File

@ -1,3 +0,0 @@
label: Unity Plugin
icon: code
order: 0

View File

@ -1,82 +0,0 @@
---
label: Introduction
icon: dot
order: 30
---
# Welcome to PocketPython
PocketPython is a C# plugin that allows you to do Python scripting in Unity. It provides a sandboxed Python environment, which adds 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.
!!!
PocketPython is designed for game scripting, not for scientific computing.
You cannot use it to run NumPy, OpenCV, or any other CPython extension modules.
!!!
## Features
### Python 3.x Syntax
PocketPython uses [pocketpy](https://github.com/pocketpy/pocketpy)
as frontend to parse and compile Python source code.
It supports most of the Python 3.x syntax.
The following table shows a feature comparison of PocketPython
with respect to the original [pocketpy](https://github.com/pocketpy/pocketpy).
The features marked with `YES` are supported, and the features marked with `NO` are not supported.
| Name | Example | Cpp | Unity |
| --------------- | ------------------------------- | --------- | --- |
| If Else | `if..else..elif` | YES | YES |
| Loop | `for/while/break/continue` | YES | YES |
| Function | `def f(x,*args,y=1):` | YES | YES |
| Subclass | `class A(B):` | YES | YES |
| List | `[1, 2, 'a']` | YES | YES |
| ListComp | `[i for i in range(5)]` | YES | YES |
| Slice | `a[1:2], a[:2], a[1:]` | YES | YES |
| Tuple | `(1, 2, 'a')` | YES | YES |
| Dict | `{'a': 1, 'b': 2}` | YES | YES |
| F-String | `f'value is {x}'` | YES | YES |
| Unpacking | `a, b = 1, 2` | YES | YES |
| Star Unpacking | `a, *b = [1, 2, 3]` | YES | YES |
| Exception | `raise/try..catch` | YES | NO |
| Dynamic Code | `eval()/exec()` | YES | YES |
| Reflection | `hasattr()/getattr()/setattr()` | YES | YES |
| Import | `import/from..import` | YES | YES |
| Context Block | `with <expr> as <id>:` | YES | NO |
| Type Annotation | `def f(a:int, b:float=1)` | YES | YES |
| Generator | `yield i` | YES | NO |
| Decorator | `@cache` | YES | YES |
### Sandboxed Python Environment
PocketPython provides a sandboxed Python environment.
All python code is executed in a C# virtual machine.
The user cannot access the file system, network, or any other resources of the host machine.
### Seamless Interop with C#
PocketPython uses `object` in C# to represent dynamic typed Python objects.
Most of the basic Python types correspond to a C# type,
which means passing arguments between C# and Python is extremely easy and intuitive.
| Python Type | C# Type |
| ----------- | ------- |
| `None` | `NoneType` |
| `object` | `System.Object` |
| `bool` | `System.Boolean` |
| `int` | `System.Int32` |
| `float` | `System.Single` |
| `str` | `System.String` |
| `tuple` | `System.Object[]` |
| `list` | `System.Collections.Generic.List<object>` |
| `dict` | `System.Collections.Generic.Dictionary<PyDictKey, object>` |
| ... | ... |
### Python Console in Editor
PocketPython provides a Python console in Unity editor,
which allows you to do quick debugging and testing.

View File

@ -1,153 +0,0 @@
---
label: Virtual machine
icon: dot
order: 20
---
The `VM` class provides a sandboxed Python environment and a set of APIs for interacting with it.
Using the namespace `PocketPython` before any operations.
```csharp
using PocketPython;
```
### Construction
+ `VM()`
Create a new Python virtual machine.
### Code Execution
+ `CodeObject Compile(string source, string filename, CompileMode mode)`
Compile Python source code into a `CodeObject` that can be executed later.
The `filename` parameter is used for error reporting, you can set it to `main.py` if you don't need it.
The `mode` parameter specifies the compile mode, see [CompileMode](../quick-start/exec/#compile-mode) for details.
+ `object Exec(CodeObject co, PyModule mod = null)`
Execute a `CodeObject` in the given module.
The `mod` parameter specifies the module in which the code will be executed.
If it is `null`, the code will be executed in the main module.
+ `object Exec(string source, string filename, CompileMode mode = CompileMode.EXEC_MODE, PyModule mod = null)`
Compile and execute Python source code in the given module. It is equivalent to `Exec(Compile(source, filename, mode), mod)`.
+ `object Eval(string source, PyModule mod = null)`
Evaluate an expression in the given module.
+ `object Call(object callable, object[] args, Dictionary<string, object> kwargs)`
Call a Python callable object with the given arguments and keyword arguments. It is equivalent to `callable(*args, **kwargs)` in Python.
+ `object CallMethod(object obj, string name, params object[] args)`
Call a method of a Python object with the given arguments. It is equivalent to `obj.name(*args)` in Python.
### Attribute Access
+ `object GetAttr(object obj, string name, bool throwErr = true)`
Get an attribute of a Python object. It is equivalent to `obj.name` in Python.
If `throwErr` is `true`, it will throw an exception if the attribute does not exist.
Otherwise, it will return `null`.
+ `NoneType SetAttr(object obj, string name, object value)`
Set an attribute of a Python object. It is equivalent to `obj.name = value` in Python.
+ `bool HasAttr(object obj, string name)`
Check if a Python object has the given attribute. It is equivalent to `hasattr(obj, name)` in Python.
### Module Access
+ `Dictionary<string, PyModule> modules`
A dictionary that maps module names to `PyModule` objects.
You can use it to access the modules that have been imported.
+ `Dictionary<string, string> lazyModules`
A dictionary stores all unimported modules. You can add Python source into this dictionary.
It will be initialized and moved to `modules` when it is first imported.
+ `PyModule NewModule(string name)`
Create a new module with the given name at runtime. The module will be added to `modules` automatically.
+ `PyModule PyImport(string name)`
Import a Python module. It is equivalent to `import name` in Python. It first checks if the module has been imported, if not, it will try to load the module from `lazyModules`.
### Type Conversion
+ `T PyCast<T>(object obj)`
Convert a Python object to a C# object. It is equivalent to `obj as T` in C#.
Raise `TypeError` if the conversion fails.
+ `bool IsInstance(object obj, PyTypeObject type)`
Check if a Python object is an instance of the given type. It is equivalent to `isinstance(obj, type)` in Python.
+ `void CheckType<T>(object t)`
Check if `t is T`. Raise `TypeError` if the check fails.
+ `bool PyEquals(object lhs, object rhs)`
Check if two Python objects are equal. It is equivalent to `lhs == rhs` in Python. This is different from `==` or `object.ReferenceEquals` in C#. You should always use this method to compare Python objects.
+ `object PyIter(object obj)`
Get an iterator of a Python object. It is equivalent to `iter(obj)` in Python.
+ `object PyNext(object obj)`
Get the next element of a Python iterator. It is equivalent to `next(obj)` in Python.
+ `bool PyBool(object obj)`
Convert a Python object to a boolean value. It is equivalent to `bool(obj)` in Python.
+ `string PyStr(object obj)`
Convert a Python object to a string. It is equivalent to `str(obj)` in Python.
+ `string PyRepr(object obj)`
Convert a Python object to a string representation. It is equivalent to `repr(obj)` in Python.
+ `int PyHash(object obj)`
Get the hash value of a Python object. It is equivalent to `hash(obj)` in Python.
+ `List<object> PyList(object obj)`
Convert an `Iterable` Python object to a list. It is equivalent to `list(obj)` in Python.
### Callbacks
+ `System.Action<string> stdout = Debug.Log`
A callback that will be called when the Python code invokes `print` function.
By default, it will print the message to Unity console.
+ `System.Action<string> stderr = null`
A callback that will be called when the Python code emits an error message.
By default, an Exception will be raised.
You can set it to `Debug.LogError` for printing to the Unity Console.
### Debug Flag
+ `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.

View File

@ -28,7 +28,8 @@ for line in lines:
output.append('```cpp\n')
with open('docs/references.md', 'w', encoding='utf-8') as f:
f.write('''---label: References
f.write('''---
label: References
icon: code
order: 2
---