gpt4 book ai didi

c++ - 如果旧工作已经完成,如何开始新工作?

转载 作者:行者123 更新时间:2023-11-30 03:45:54 26 4
gpt4 key购买 nike

我正在编写一个小游戏,我想将我的渲染器与主循环分离。在主循环中,我想更新我的输入,我不想等到我的渲染器完成绘制,但这意味着我只想在渲染器完成绘制时发出绘制命令。

我需要一种方法来了解旧的渲染作业是否已完成,以便我可以开始新的渲染作业。

#include <asio.hpp>
#include <memory>
#include <thread>
#include <iostream>
#include <mutex>
#include <chrono>
struct ready
{
bool is_ready;
std::mutex m;
void set(bool b)
{
std::lock_guard<std::mutex> g(m);
is_ready = b;
}
operator bool()
{
std::lock_guard<std::mutex> g(m);
return is_ready;
}
ready()
: is_ready(true)
{
}
};

int
main()
{
auto service = std::make_shared<asio::io_service>();
auto w = std::make_shared<asio::io_service::work>(*service);
std::thread t1([&] { service->run(); });
std::thread t2([&] { service->run(); });
auto ready_sp = std::make_shared<ready>();
while (ready_sp) {
if (*ready_sp) {
ready_sp->set(false);
service->dispatch([ready_sp] {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Draw on thread: " << std::this_thread::get_id()
<< std::endl;

ready_sp->set(true);
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Doing other stuff" << std::endl;
}
w.reset();
t1.join();
t2.join();
}

这就是我的大致做法吗?

最佳答案

好的,下面是我可能如何处理它的示例。

此代码有一个线程用于渲染(但我们可以使用更多线程)并使用主线程来玩游戏。

我已将代码拆分为多个关注点:

scene是描述场景状态的数据

render 是渲染场景的免费函数。它不知道线程、锁、互斥锁或内存管理。

renderer 是一个在其自己的线程中渲染场景的对象,但仅在被告知时才这样做。

scene_update 封装了对场景进行大量增量更新的概念,但渲染器应该只在所有增量完成后才被要求渲染 - 即原子更新。

本例模拟场景更新计算耗时300ms,实际渲染耗时1s。因此,我们应该看到每 ~3 次更新 1 次渲染。

希望您会同意,由于整个程序使用值语义 并封装了所有内存管理和线程问题,所以程序的主体非常易于阅读。

主要是:

int main()
{
using namespace std;

// create my scene
scene my_scene;

// instantiate my renderer
renderer my_renderer;

// tell the renderer that the scene may be rendered
my_renderer.notify(my_scene);

// ... while it is doing that...
// ... lets make our hero march across the wilderness
for (int x = 0 ; x < 10 ; ++x)
{
for(int y = 0 ; y < 10 ; ++y)
{
// perform a scene update. the calculations for this update
// take 300ms (faster than the renderer)
scene_update u(my_scene, my_renderer);
{
my_scene.data().hero_x = x;
my_scene.data().hero_y = y;
this_thread::sleep_for(chrono::milliseconds(300));
}
// tell the renderer that there is a new scene to render
u.commit();
}
}

return 0;
}

同样render也很简单:

void render(const scene& s)
{
using namespace std;

const auto& data = s.data();

cout << "the hero is at ";
cout.flush();
this_thread::sleep_for(chrono::milliseconds(500));

cout << data.hero_x << ", ";
cout.flush();
this_thread::sleep_for(chrono::milliseconds(500));

cout << data.hero_y << '\n';
cout.flush();
}

完整程序如下:

#include <iostream>
#include <vector>
#include <string>
#include <condition_variable>
#include <thread>
#include <memory>
#include <cassert>


//
// a simple scene object with *value semantics*
// the actual scene data is stored in an inner class, an instance of which is maintained by a unique_ptr
// we could have used a shared_ptr but there is no reason to since we will be taking copies of the scene
// data in order to render it out of line.
// doing it this way means that although the copy might be expensive, it is only performed once
// moves are extremely fast
struct scene
{
// a type to allow us to create an unitialised scene explicitly
struct none_type {};

// a flag object
static constexpr const none_type none = none_type();

// this is the actual expensive scene data (simulated)
struct expensive_large_scene_data
{
int hero_x = 0,
hero_y = 0;
};

// a printer function (to help debugging)
friend std::ostream& operator<<(std::ostream& os, const expensive_large_scene_data& s)
{
os << "(" << s.hero_x << ", " << s.hero_y << ")";
return os;
}

// construct empty
scene(none_type) {
// no not initialise the pointer
}

// construct and initialise a default scene
scene() : _data(std::make_unique<expensive_large_scene_data>()) {}

// copy constructor must explicitly clone the pointer (if populated)
scene(const scene& r)
: _data(r
? std::make_unique<expensive_large_scene_data>(r.data())
: nullptr)
{}

// move constructor
scene(scene&& r)
: _data(std::move(r._data))
{}

// copy-assignment - take care here too.
scene& operator=(const scene& r)
{
_data = r
? std::make_unique<expensive_large_scene_data>(r.data())
: nullptr;
return *this;
}

// move-assignment is simple
scene& operator=(scene&& r)
{
_data = std::move(r._data);
return *this;
}

// no need for a destructor - we're using unique_ptr

bool valid() const {
return bool(_data.get());
}

// convertible to bool so we can check whether it is empty easily

operator bool() const {
return valid();
}

void reset() {
_data.reset();
}

// accessor

const expensive_large_scene_data& data() const {
assert(_data.get());
return *_data;
}

expensive_large_scene_data& data() {
assert(_data.get());
return *_data;
}

private:
std::unique_ptr<expensive_large_scene_data> _data;
};


std::ostream& operator<<(std::ostream& os, const scene& s)
{
return os << s.data();
}


// a function that renders a scene
// this one takes a second to complete
void render(const scene& s)
{
using namespace std;

const auto& data = s.data();

cout << "the hero is at ";
cout.flush();
this_thread::sleep_for(chrono::milliseconds(500));

cout << data.hero_x << ", ";
cout.flush();
this_thread::sleep_for(chrono::milliseconds(500));

cout << data.hero_y << '\n';
cout.flush();
}

// the renderer
struct renderer
{
using mutex_type = std::mutex;
using lock_type = std::unique_lock<mutex_type>;

// start thread in constructor - do not copy this object (you can't anyway because of the mutex)
renderer()
: _render_thread(std::bind(&renderer::loop, this))
{}

// shut down cleanly on destruction
~renderer()
{
auto lock = lock_type(_mutex);
_cancelled = true;
lock.unlock();

if (_render_thread.joinable())
{
_render_thread.join();
}
}

// notify the renderer that a new scene is ready
void notify(const scene& s)
{
auto lock = lock_type(_mutex);
_pending_scene = s;
lock.unlock();
_cv.notify_all();
}

private:
void loop()
{
for(;;)
{
auto lock = lock_type(_mutex);
_cv.wait(lock, [this] {
// wait for either a cancel event or for a new scene to be ready
return _cancelled or _pending_scene;
});

if (_cancelled) return;

// move the pending scene to our scene-render buffer - this is very cheap
_current_scene = std::move(_pending_scene);
_pending_scene.reset();
lock.unlock();

// unlock early to allow mainline code to continue


// now take our time rendering the scene
render(_current_scene);
_current_scene.reset();
}
}

private:
mutex_type _mutex;
std::condition_variable _cv;
bool _cancelled = false;
scene _pending_scene = scene(scene::none);
scene _current_scene = scene(scene::none);
std::thread _render_thread;
};

// an object to connect a scene update 'transaction' with the renderer
struct scene_update
{
scene_update(scene& s, renderer& r)
: _s(s), _r(r) {}

void commit()
{
_r.notify(_s);
}

scene& _s;
renderer& _r;
};



int main()
{
using namespace std;

// create my scene
scene my_scene;

// instantiate my renderer
renderer my_renderer;

// tell the renderer that the scene may be rendered
my_renderer.notify(my_scene);

// ... while it is doing that...
for (int x = 0 ; x < 10 ; ++x)
{
for(int y = 0 ; y < 10 ; ++y)
{
// perform a scene update. the calculations for this update
// take 300ms (faster than the renderer)
scene_update u(my_scene, my_renderer);
{
my_scene.data().hero_x = x;
my_scene.data().hero_y = y;
this_thread::sleep_for(chrono::milliseconds(300));
}
// tell the renderer that there is a new scene to render
u.commit();
}
}

return 0;
}

预期输出:

the hero is at 0, 0            
the hero is at 0, 2 <<-- note the missing updates
the hero is at 0, 5 <<-- because rendering takes longer
the hero is at 0, 8 <<-- than calculation
the hero is at 1, 2

关于c++ - 如果旧工作已经完成,如何开始新工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34434733/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com