目次

PyO3でLÖVEもどきを作る (2)

作成日: 2023-08-14 (月)

第9回 PyO3でLÖVEもどきを作る (2)

プロジェクト pyove

Cargo.toml

[package]
name = "pyove"
version = "0.1.0"
edition = "2021"

[dependencies.pyo3]
version = "0.19.2"
features = ["auto-initialize", "macros"]

src/main.rs

use pyo3::prelude::*;
use std::{env, path::Path, process::exit};

pub mod graphics;

#[pyfunction]
fn load() {
    println!("default load");
}

#[pyfunction]
fn draw() {
    println!("default draw");
}

#[pyfunction]
fn update(dt: f32) {
    println!("default update {}", dt);
}

#[pymodule]
fn pyove(py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(load, m)?)?;
    m.add_function(wrap_pyfunction!(draw, m)?)?;
    m.add_function(wrap_pyfunction!(update, m)?)?;
    graphics::register_graphics_module(py, m)?;
    Ok(())
}

fn main() -> PyResult<()> {
    //
    // コマンドライン引数のパース
    //
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        // コマンドライン引数がただ1つだけ与えられていなければ終了する
        eprintln!("Usage: {} GAMEDIR", args[0]);
        exit(1);
    }

    let gamedir = Path::new(&args[1]);
    let main_file = gamedir.join("main.py");
    if !main_file.exists() {
        // main.pyが存在しなければ終了する
        eprintln!("Error: {} not exists.", main_file.to_str().unwrap());
        exit(2);
    }

    // main.pyの内容を読み込む
    let main_code = std::fs::read_to_string(main_file)?;

    //
    // activate Python interpreter
    //

    // pyoveをモジュールとして登録
    pyo3::append_to_inittab!(pyove);

    Python::with_gil(|py| {
        // ゲームディレクトリをインポートパスに追加する
        let sys_path_code = format!(
            "import sys; sys.path.append('{}')",
            gamedir.to_str().unwrap()
        );
        py.run(&sys_path_code, None, None)?;

        // pyoveモジュールにアクセスしたいので、ここでインポートする
        let pyove = PyModule::import(py, "pyove")?;

        // main.pyを実行
        py.run(&main_code, None, None)?;

        // main.pyでこの3つの関数は置き換え可能
        let load_fn = pyove.getattr("load")?;
        let update_fn = pyove.getattr("update")?;
        let draw_fn = pyove.getattr("draw")?;

        // 試しにloadとupdateとdrawを実行してみる
        load_fn.call0()?;
        update_fn.call1((123,))?;
        draw_fn.call0()?;

        Ok(())
    })
}

src/graphics.rs

//
// このプロジェクトではset_colorとrectangle関数のみを提供する
//
use pyo3::prelude::*;

#[pyfunction]
#[pyo3(name = "setColor")]
fn set_color(r: f32, g: f32, b: f32) {
    // 仮の処理
    println!("setColor {} {} {}", r, g, b);
}

#[pyfunction]
fn rectangle(mode: &str, x: i32, y: i32, w: i32, h: i32) {
    // 仮の処理
    println!("rectangle {} {} {} {} {}", mode, x, y, w, h)
}

pub fn register_graphics_module(py: Python<'_>, parent_module: &PyModule) -> PyResult<()> {
    // see: https://pyo3.rs/v0.19.2/module#python-submodules
    // サブモジュールとしてgraphicsを登録する
    // graphicsに、setColorとrectangleを登録する
    let m = PyModule::new(py, "graphics")?;
    m.add_function(wrap_pyfunction!(set_color, m)?)?;
    m.add_function(wrap_pyfunction!(rectangle, m)?)?;
    parent_module.add_submodule(m)?;
    Ok(())
}

example/main.py

import pyove
#import foo # test import availability

# Python requires global scoped variable definitions
x, y, w, h = 0, 0, 0, 0

def load():
    global x, y, w, h
    x, y, w, h = 20, 20, 60, 20

def update(dt):
    global w, h
    w += 1
    h += 1

def draw():
    pyove.graphics.setColor(0, 0.4, 0.4)
    pyove.graphics.rectangle("fill", x, y, w, h)

# overwrite default module functions
pyove.load = load
pyove.draw = draw
pyove.update = update

参考

オリジナルの動作は、こちらを参考にしてください。