Git Product home page Git Product logo

rgmodern's People

Contributors

gxm11 avatar krimiston avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

rgmodern's Issues

C++层捕捉RGM内部脚本抛出的异常

【版本】
0.9.0 (commit c16c9ff)

【问题描述】
kernel_ruby.hpp 的 run 函数,在嵌入包模式下,未捕捉内部脚本抛出的异常,导致报错打印非常不可读(似乎会打印某种 dump 格式)。

【期望】
嵌入包模式下,内部脚本抛出异常后,C++ 层打印且只打印异常的 message 和 backtrace。

控帧改为等待信号实现

【版本】
0.9.0 (commit 40ef548)

【问题描述】
rgm::base::timer.tick 函数,使用死循环控帧。此实现会使 CPU 持续空跑,浪费 CPU 资源。

【期望】
实现改为操作系统的信号机制,例如 Windows 上的 WaitableTimer 系列。优先考虑跨平台实现,如果没有方便的跨平台库,则分别为 Windows 与 UNIX 写实现。

Bitmap#get_pixel的处理

现在使用了 SDL_RenderReadPixels 来实现,但是经测试只在 OPENGL 渲染时能正确返回。Direct3d11渲染不能正确返回,有人提过issue并给出了patch(见https://github.com/libsdl-org/SDL/issues/4782),但实测无效。对于d3d11,可能要求开发者:

  1. 没能力修改第三方脚本的,使用opengl
  2. 有能力修改脚本的,江特定几处Bitmap换成Palette
  3. 确保没有get_pixel的,使用d3d11。但是因为测试不可能详尽,为避免玩家游戏崩溃,RGM_BUILD_MODE大于等于2就返回透明像素,小于2就报错(方法不存在或者deprecated)。

这些需要等到渲染器可以在运行时选择了再做比较合适。

此时可以顺便考虑引入DX9,相关的shader可以在旧代码里找到,并且很可能get pixel是没问题的。

机器学习预测delay

python代码如下:

import matplotlib.pyplot as plt
from random import random


def target(x):
    return x + 0.1 * x - 0.01 * x * x + 0.1 * random() + 0.03


def target2(x):
    return x + 0.1 * x - 0.02 * x * x + 0.1 * 0.5 + 0.03


model = [0, 1, 0, 0, 0, 0]


def predict_delay(model, delay):
    sum = 0
    t = 1
    for w in model:
        sum += w * t
        t = t * delay
    return min(delay, max(0, sum))


def train_model(model, delay, real_delay):
    lr = 0.01
    gamma = 0.001
    old_model = [w for w in model]
    s = sum([w * w for w in old_model]) - 1
    x, y = real_delay, delay
    for i, w in enumerate(old_model):
        model[i] -= lr * \
            (predict_delay(old_model, x) - y) * (x ** i)
        model[i] -= lr * gamma * s * w


for i in range(3600):
    x = random()
    train_model(model, x, target(x))

xs = []
ys = []
ys2 = []
for i in range(100):
    x = i / 100

    delay = predict_delay(model, x)
    real_delay = target(delay)
    train_model(model, delay, real_delay)

    xs.append(x)
    ys.append(target(x))
    ys2.append(target2(delay))

plt.figure()
plt.plot(xs, ys, "b*")
plt.plot(xs, ys2, "rx")
plt.plot([0, 1], [0, 1], "k--")
plt.axis([0, 1, 0, 1])
plt.savefig("4.png")
plt.close()

print(model)

其中target是未知的目标函数,输入值是准备传给sleep的delay,输出值是实际的real_delay。target2是无随机扰动的版本。

使用了多项式模型来用real_delay(x)预测delay(y):y = a0 + a1 * x + a2 * x^2 + ... + an * x^n。模型有初始化的值:除了a1 = 1,其他系数都为0。模型要求输入值在0~1之间,所以对real_delay要除以当前的帧间隔处理(real_delay不可能大于当前的帧间隔)。

训练的loss函数采用了平方损失函数,并且附带了正则项:((a0^2 + a1^2 + ... + an^2) - 1) ^2,此项的目的是使所有系数的平方和趋于1。

最后展示的结果是在经过3600次的预训练之后,预测函数的表现。第一张图移除了随机值以便观测。

横坐标是expect_delay,纵坐标是real_delay。蓝色相当于直接sleep(expect_delay),所以real_delay总是大一点;红色相当于sleep(predict(expect_delay)),可见红色已经消除了蓝色的bias,不考虑随机性的情况下,real_delay与expect_delay非常接近。
4-norandom
4

【feat】请求新增 AudioContext

AudioContext:对ruby脚本可见的音频上下文对象,一个对象对应一段音频实例,可以通过脚本即时获取该音频相关的信息、状态,以及控制。

控制器与输入法的冲突

在范例中,如果使用控制器,并且打开了输入法测试的箱子,在输入法结束后控制器的按键不可用。重新拔出再插入控制器也会导致控制器按键不可用,看上去重新拔出后控制器的编号变成了1、2而不是始终为0。

使用新的worker合作模式

准备参考erlang的设计,使用”Actor“模式,给worker分组,每组在同一个线程执行,组间以协程方式执行。

erlang教程:https://www.tutorialspoint.com/erlang/index.htm

以下几点可能要考虑:

  1. 被动线程可以简化操作,不参与协程,都是就地执行。但如果是其他线程的被动线程,还是要发送任务。
  2. group中一个worker退出会导致其他worker一起退出,但是不会导致其他的group退出。
  3. wait同一个group可以忽略,但是wait其他的group的worker需要真的等待。

1.1.0 new core design

新的RGM核心调度规则

20230717 运行流程设计

  1. engine包含多个scheduler,每个scheduler在独立的线程里运行。

  2. scheduler在独立线程中执行,它初始化后就会进入主循环。在没有worker的情况下,scheduler不会退出。

  • scheduler可以添加、删除worker,然后在主循环中调度这些worker。添加worker是即时的,但删除worker是异步的,worker在收到退出信号后,会自行退出。
  • worker的退出不影响scheduler,则任务必须以广播的形式发送。这样任务不会被拦截,单个worker的存在与否不影响其他的worker是否执行任务。
  • 与之前的设计不同,scheduler的数据结构里没有业务逻辑。只在添加了不同的worker后才会执行特定的业务逻辑。
  • 由于scheduler没有业务逻辑,所以广播的任务scheduler一定会收到,不过在主循环启动后会迅速被丢弃。
  1. scheduler的主循环分为以下步骤:
  • 检查scheduler和worker的状态,移除已经结束的worker
  • 检查任务队列queue,将queue中的任务发布给其他的worker
  • 依次执行worker的业务逻辑
  1. scheduler在多次检查自己的任务队列都为空时,就使用阻塞模式读取任务队列的内容,且等待至多1ms,以降低CPU消耗。
  • 这是整个scheduler的主循环里唯一的等待。
  1. scheduler对worker的调度有4种模式,区别在于worker是否在fiber中运行,以及是否定义run函数。简单地说,如果worker在fiber中运行,有独立的运行栈,则worker流程阻塞时,会切换到主要fiber;如果没有独立的运行栈,则阻塞worker的同时,会定期更新scheduler,并嵌套执行其他的worker。
分类 run_in_fiber 定义run函数 调度方式
1 Yes Yes 给worker创建新的fiber,执行run函数。
2 Yes No 给worker创建新的fiber,执行默认的run函数。此run函数会清空队列,在队列为空或获取数据失败时使用fiber_yield切回到主Fiber。
3 No Yes worker的run函数直接阻塞scheduler的运行。这仅适用于scheduler中只有1个worker的情况。
4 No No 在主Fiber中,worker会执行默认的run函数。此函数会清空队列,在队列为空或获取数据失败时就会提前结束。
  1. worker有自己的可执行任务列表,不在列表中的任务将不会分发给worker。worker有一个队列,其中的任务会从前到后依次执行,队列的实现是std::queue。

  2. worker的运行流程如下,注意,如果worker在fiber中执行,下面的每个都是在切换到fiber后执行的。

  • scheduler添加worker,创建基本的数据结构,随后的此操作都是将控制权交给worker执行的。
  • 初始化owner类型的数据
  • 执行数据的setup_owner函数,无需获取数据所有权,在这步函数执行完毕后,owner数据才可以被其他worker加载。
  • 初始化local类型的数据
  • 初始化group类型的数据
  • 初始化share类型的数据
  • 执行数据的setup函数,需要获取数据所有权
  • 执行run函数或者清空队列,在合适的时机将执行权交给scheduler
  • 进入结束状态后,会先解绑数据再退出
  • 解绑share类型的数据
  • 解绑group类型的数据
  • 析构local类型的数据
  • 析构owner类型的数据,需要获取数据所有权
  • 清空队列中的任务,但是不锁定数据,也不执行run的内容(在此处解除其他worker的等待)
  • 完全结束,将执行权交给scheduler,后者会在下次更新时删除此worker。

当worker获取的share或group数据的owner已经析构时,worker会进入退出状态。

  1. worker执行任务时,如果定义了run函数,则需要在run函数调用过程中,主动广播任务或者阻塞执行任务。如果未定义run函数,则会读取任务队列的首项并执行,然后移除此项,直到任务队列为空,或者由于其他原因交出执行权。
  • 在此worker执行任务,需要锁定数据,在锁定数据失败时阻塞worker的流程。
  • 发送任务到别的worker,任务会异步执行。
  • 发送任务到别的worker,并等待相应的任务结束,此时worker的流程阻塞。建议监控某个变量,如果变量从true变为false则认定任务结束。
  1. worker流程阻塞时,会交出执行权。如果worker在fiber中运行,会切换回主要fiber,执行scheduler的主循环;如果worker不在fiber中运行,scheduler仍然可以执行单步更新,分配任务并执行其他的worker的流程。
  • 这里不需要考虑两个worker互相等待对方的死锁问题,因为即使是多线程的模式,也无法解决这个死锁,这属于业务逻辑上的错误设计。
  • scheduler可以嵌套调用worker,方法是在worker阻塞时,调用scheduler的update方法。
  • 在必要的时候,worker也可以使用原子变量atomic_wait等待调度,这样会导致scheduler随着一起阻塞。
  1. 任务有自己的run函数和成员变量。run函数的参数是worker中数据的引用或者常量引用,worker在执行run函数之前会尝试锁定任务所需的数据,如果失败则阻塞流程,交出执行权,等待下次恢复运行时再次尝试锁定数据。run函数执行完毕后,解锁刚刚锁定的数据。
  • 任务必须是可复制的,每个任务都会广播给所有的scheduler。
  • 在任务中执行任务,则无需锁定全部的数据,通过模板参数告知worker哪些数据要锁定。
  • 专用于阻塞流程的任务,需要用std::function给出判断条件,比如捕获一个栈上的变量,每次worker运行到此处时检查判断条件是否返回false或者true,决定是否继续运行。

综上,需要实现以下类和函数:

1. engine
- broadcast_task<T_task>(const T_task& t)
- terminate()

2. scheduler
- create_worker<T_worker>()
- remove_worker(size_t worker_id)
- update()
- enqueue_task<T_task>(const T_task& t)
- dequeue_task()
- resume_worker()
- terminated()

3. worker
- lock_data<T_data ...>()
- unlock_data<T_data ...>()
- run_task<T_task>()
- yield()
- setup_owner()
- setup_data()
- bind_data()
- unbind_data()
- terminate()

4. task
- run(T1& t1, T2& t2, ...)

与 Rake 和 Gemfile 协同

好奇 如果 ruby 侧通过 Gemfile 引入其他的 gem 脚本,要如何同 RGM 协作?
是否提供对 gemfile 的支持和打包?

引入协程,添加 concurrent 合作模式

调研发现这个库虽然很老但是可以用:https://github.com/paladin-t/fiber
以下代码在windows(msys2 ucrt环境)和linux(Debian GNU/Linux 11 (bullseye))下测试均通过:

#include <cstdio>
#include <vector>

#include "fiber.h"

bool stop = false;

struct worker {
  fiber_t* fb_scheduler;

  int index;

  void run() {
    int x = 0;
    while (true) {
      ++x;
      printf("worker %d run %d.\n", index, x);
      if (x == 5) {
        stop = true;
        break;
      }
      yield();
    }
    yield();
  }

  void yield() { fiber_switch(fb_scheduler); }
};

std::vector<worker> workers;

struct scheduler {
  fiber_t* fb_self;
  std::vector<fiber_t*> fibers;

  template <size_t id>
  static void run(fiber_t*) {
    workers[id].run();
  }

  scheduler() {
    workers.resize(3);

    fb_self = fiber_create(0, 0, 0, 0);
    int index = 0;
    for (auto& w : workers) {
      w.index = (index++);
      w.fb_scheduler = fb_self;
    }

    fibers.push_back(fiber_create(fb_self, 0, run<0>, 0));
    fibers.push_back(fiber_create(fb_self, 0, run<1>, 0));
    fibers.push_back(fiber_create(fb_self, 0, run<2>, 0));
  }

  void run() {
    printf("start\n");
    for (size_t i = 0; i < 10; ++i) {
      if (stop) break;

      printf("run %lld\n", i);
      for (size_t j = 0; j < fibers.size(); ++j) {
        auto fb = fibers[j];
        printf("switch to %lld\n", j);
        fiber_switch(fb);
        printf("switch back\n");
      }
    }
    printf("end\n");
  }
};

int main() {
  scheduler s;
  s.run();
}
// g++ -o main.exe main.cpp

输出:

start
run 0
switch to 0
worker 0 run 1.
switch back
switch to 1
worker 1 run 1.
switch back
switch to 2
worker 2 run 1.
switch back
run 1
switch to 0
worker 0 run 2.
switch back
switch to 1
worker 1 run 2.
switch back
switch to 2
worker 2 run 2.
switch back
run 2
switch to 0
worker 0 run 3.
switch back
switch to 1
worker 1 run 3.
switch back
switch to 2
worker 2 run 3.
switch back
run 3
switch to 0
worker 0 run 4.
switch back
switch to 1
worker 1 run 4.
switch back
switch to 2
worker 2 run 4.
switch back
run 4
switch to 0
worker 0 run 5.
switch back
switch to 1
worker 1 run 5.
switch back
switch to 2
worker 2 run 5.
switch back
end

可见非常完美地实现了协程调度,每次worker调用 yield() 函数时,切换到下一个 worker 执行。由于是单线程模式,被动worker仍然不执行,只有主动worker可以执行,并主动 yield() 放弃执行权。主动 worker 在 flush 前会自动 yield,确保每次以flush开始新的执行。

升级ruby到3.2.1

ruby32分支已创建,这里存一下ruby编译后的二进制文件:
vendors_2.zip
因为github不能上传7z,就用zip包装了一下

opengl的tilemap有绘制bug

opengl下的tilemap存在一定的绘制bug:

  • 部分图块显示的内容错误,并且随着玩家的移动,或者时间变化错误的内容也变化
  • 部分图块显示的内容并不是其他的图块,而是错误的彩色信息

目前在测试工程的大地图上发现了这些bug,另外3张小地图没有问题。菜单打开也正常。

经测试bug表现跟tileset有关,可以更换此地图的tileset查看具体效果,尤其是使用第5套海边道路的tileset时,会导致整个画面全部渲染成白色。

目前怀疑是跟自动元件的数量有关,可能tilemap写的部分优化代码有问题,等待进一步排查。

Game.exe不能在中文路径下运行

报错信息如下:

INFO: RGModern starts running...
WARN: [Kernel] the cooperation type is exclusive.
WARN: [Driver] use direct3d for rendering
WARN: [Driver] max texture 8192 x 8192
start ruby.
resource prefix = resource://
render driver = direct3d9 (2)
build mode = 2
WARN: [Input] text input is stopped
ERROR: Finder cannot find valid path for Data/Scripts.rxdata
ERROR: ERROR: Failed to load embeded script `script/main.rb'.
ERROR: Failed to load embeded script `script/load.rb'.

ruby:in `load_script'
exit ruby.
INFO: RGModern ends with applause.

初步判断是读 Data/Scripts.rxdata 时,Finder出了问题。相关代码如下:

Load_Path[key].each do |directory|
      Suffix[key].each do |extname|
        path = File.expand_path(filename + extname, directory)
        if File.exist?(path)
          Cache[filename] = path
          return Cache[filename]
        end
      end
    end

可能是expand_path的问题,也可能是 exist? 的问题。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.