Tutorial/modding architecture/zh

From OpenComputers: Rebooted
Jump to navigation Jump to search

模组开发:自定义架构

在此文章中我会讲解OC模组的架构是如何工作的,以及如何添加自定义架构。一句警告:架构的实现并非易事,别指望着轻而易举就能完成。

架构是什么?

首先让我们来申明OC模组语境下的“架构”是什么。架构指将某种代码执行器(以下称虚拟机)和OC模组的组件/Minecraft结合起来的程序。OC将组件逻辑与电脑逻辑严格分开。换言之,组件(例如显卡)不知道它自身在使用什么架构。反过来,架构也不知道组件如何工作。传递的一切信息都由执行机处理。执行机驱动架构,并提供回调函数使架构能与外界通信。

使用架构

为了让内容不那么难以理解,这里将讲解如何实际应用架构。这大概能让读者更好地理解架构实现是怎么融入OC模组生态的。要使用架构,则必须创建一个使用此架构的执行机。

  • 注意:OC 1.4版本后,附属模组可以注册通过CPU提供的架构,因此你无需添加一种电脑方块/物品了。可以通过手持CPU时潜行右键单击来切换其提供的架构。

执行机封装了架构,并负责线程调度、信号队列、网络连接等。执行机创建好以后,你只需每tick调用一次`update()`函数(还可能需要向其传递一些调用,例如`start()`)。

生命周期

架构的生命周期大致为:实例化,`initialize()`,反复执行`runThreaded()` / `runSynchronized()`,`close()` [, 转到 `initialize()`]。

  • 执行机被创建时,架构即被创建。 - 执行机启动或重启时,会要求`initialize()`自身。通常来说代表着创建底层虚拟机。 - 驱动架构的标准方法是通过`runThreaded()`函数,它由执行器线程调用。架构的状态应在此处处理。其返回值类型被执行机用于调度。注意:此方法不应抛出异常。你有责任处理所有错误,并在需要的时候将它们转换为代表报错的返回值。 - 需要有一个特殊执行结果来表示进行“同步调用”。这个返回值会告诉执行机下次需要用`runSynchronized()`来驱动架构。这种调用一定会由MC服务端线程执行,代表着可以安全地在此回调函数中与世界交互。 - 执行机停止时会调用`close()`函数。然后释放所有打开的资源。

提供上下文

在最简单的样例中,每次调用`runThreaded()`时在你的虚拟机中仅仅进行若干`run`调用。不过这样做实际用处不大,因为你会想要以某种方式与外界通信。为了实现此功能,需要将与组件的通信提供给虚拟机。你需要在虚拟机内定义用于与执行机API通信的API(例如:`components()`和`invoke()`)。

同步调用与调用上限

组件可能有一些回调函数声明为“直接调用”,此外它们可能还会定义“调用上限”。默认情况下,所有的组件回调函数都是同步调用,即架构必须保证这些函数只能由`runSynchronized()`调用。 直接调用的调用上限限制了单个执行机每tick调用某一个函数的次数。限制本身由执行机的`invoke`方法确保实现,此方法会在调用次数过多时抛出`LimitReachedException`异常。此时架构应当短暂休眠,或者改为执行非同步调用。

  • 注意:在写这段内容的时候,我发现我忘记了把一些执行检查所需的方法放入API中。这将在下一个API更新中得到修复,使这些方法在`Component`接口中可用。现在的话,要么将它们反射进去,要么从OC源码自行构建模组。我对此表示抱歉。

样例代码

假设你有能提供以下接口的虚拟机:

```java /**虚拟机自身。这里的仅为一个样例,不是“真的”接口。*/ public interface PseudoVM {

Object[] run(Object[] args) throws Exception;
void setApiFunction(String name, PseudoNativeFunction value);

}

/**定义了主机提供的回调的接口。*/ public interface PseudoNativeFunction {

Object invoke(Object[] args);

} ```

一个简单的架构实现大概看起来像这样子:

```java /**这是你实现的类;这里的架构来自OC的API。*/ @Architecture.Name("Pseudolang") public class PseudoArchitecture implements Architecture {

private final Machine machine;
private PseudoVM vm;
/**构造函数的签名必须与此处完全一致。*/
public PseudoArchitecture(Machine machine) {
  this.machine = machine;
}
public boolean isInitialized() { return true; }
public void recomputeMemory() {}
public boolean initialize() {
  //在此处新建虚拟机,并在里面注册你想要的所有API回调函数。
  vm = new PseudoVM();
  vm.setApiFunction("invoke", new PseudoNativeFunction() {
    public Object invoke(Object[] args) {
      final String address = (String)args[0];
      final String method = (String)args[1];
      final Object[] params = (Object[])args[2];
      try {
        return new Object[]{true, machine.invoke(address, method, params)};
      }
      catch (e LimitReachedException) {
        //这里的执行逻辑也可用于休眠/执行同步调用。
        //在此样例中我们遵循这样的协议:
        //成功时返回(true,某物),到达上限时返回(false)。
        //此后虚拟机中运行的脚本需要将控制权交还给
        //初始化了当前执行的任务的调用者(例如,若支持的话
        //可以yield,或者在事件驱动的系统中直接return)
        return new Object[]{false};
      }
    }
  });
  vm.setApiFunction("isDirect", new PseudoNativeFunction() {
    public Object invoke(Object[] args) {
      final String address = (String)args[0];
      final String method = (String)args[1];
      final Node node = machine.node().network().node(address);
      if (node instanceof Component) {
        final Component component = (Component) node;
        if (component.canBeSeenFrom(machine.node())) {
          final Callback callback = machine.methods(node.host()).get(method);
          if (callback != null) {
            return callback.direct();
          }
        }
      }
      return false;
    }
  });
  // ... 更多回调。
  return true;
}
void close() {
  vm = null;
}
ExecutionResult runThreaded(boolean isSynchronizedReturn) {
  //在此处执行所需步骤。通常你会希望通过将队列中下一个信号传递给
  //虚拟机的方式来唤醒它,但你也可能会选择让你的虚拟机手动拉取信号。
  try {
    final Signal signal;
    if (isSynchronizedReturn) {
      //正在从同步调用中返回时不要拉取信号!因为我们正在执行其他事情。
      signal = null;
    }
    else {
      signal = machine.popSignal();
    }
    final Object[] result;
    if (signal != null) {
      result = vm.run(new Object[]{signal.name(), signal.args()});
    }
    else {
      result = vm.run(null);
    }
    //你可能会想定义一些内部协议,用以决定何时执行同步调用。
    //假设我们希望虚拟机在出现待决定的同步调用时返回数字值,代表休眠
    //或者返回布尔值,代表关机/重启或其他东西。
    if (result != null) {
      if (result[0] instanceof Boolean) {
        return new ExecutionResult.Shutdown((Boolean)result[0]);
      }
      if (result[0] instanceof Integer) {
        return new ExecutionResult.Sleep((Integer)result[0]);
      }
    }
    //若返回此值,下次'resume'时会调用runSynchronized。
    //此次调用后的下次对runThreaded函数的调用中
    //isSynchronizedReturn参数将会设定为true。
    return new ExecutionResult.SynchronizedCall();
  }
  catch (Throwable t) {
    return new ExecutionResult.Error(t.toString);
  }
}
void runSynchronized() {
  //同步调用在MC的服务端线程中执行,让回调与世界交互更方便
  //(因为它们之间的同步由执行机/架构完成)
  //这意味着若虚拟机中的代码开始了一次同步调用,就需要''暂停''
  //并且放弃对主机的控制,然后我们切换到同步调用模式(参看runThreaded),
  //并等待MC服务端线程,接着再进行实际的调用。
  //可以在runThread中传递调用所需的信息,将其存储在架构中,
  //然后在此处直接进行调用。
  //对此样例而言,让我们假定状态信息存储于虚拟机内部,并且
  //下次resume使其进行''实际''调用。下面给出了处理它们的伪代码。
  vm.run(null);
}
void onConnect() {}
//用这行代码加载虚拟机状态,假如虚拟机可持续。
void load(NBTTagCompound nbt) {}
//用这行代码保存虚拟机状态,假如虚拟机可持续。
void save(NBTTagCompound nbt) {}

} ```

一些用于在虚拟机中处理同步调用的伪代码:

```scala private def invokeSynchronous(address, method, ...) {

yield; //此处为返回到runThreaded()的地方。
 //此处为进入runSynchronized();的地方
val result = native.invoke(address, method, ...);
//查看initialize()中invoke的定义以获取结果值的信息。
yield; //并返回runSynchronized();
//并且下一个runThreaded()又进入了。
return result[1];

} private def invokeDirect(address, method, ...) {

val result = native.invoke(address, method, ...);
//查看initialize()中invoke的定义以获取结果值的信息。
if (result[0] == true) {
  return result[1];
}
else {
  return invokeSynchronous(address, method, ...);
}

} def invoke(address, method, ...) {

if (isDirect(address, method)) {
  return invokeDirect(address, method, ...);
}
else {
  return invokeSynchronous(address, method, ...);
}

} ```

注册架构

这一步非常简单,只需调用`li.cil.oc.api.Machine.add(PseudoArchitecture.class)`即可。注册架构后CPU即可提供被注册的架构。如果你不想让OC模组的CPU提供你的架构,那么你可以添加自己的`Processor`驱动程序以用于访问架构。或者更进一步,添加你自己的电脑方块,专用于运行你的架构。

目录

< 100% 15% 15% >
教程 模组特有内容 基础电脑 - 编写代码 - 硬盘 - Autorun与Startup自启动脚本
::: 模组制作 自定义架构 - IMC 信息 - OC 1.3 中的API改动 - OC 1.4 中的API改动 - 从源码构建并运行 master-MC1.7.10 版本的 OC
::: 程序 OPPM - install
::: 其它 自定义操作系统 - 中文翻译守则