(存了很久的草稿,忘记发出来)
上一次我们获取了 带有分数的 + 带有决赛室标识的 决赛排序文件(秩序册),现在我们需要将其展示出来以供查询,并且打印其以流动展示。(可以考虑使用线程对十七个决赛室的信息同时进行处理)
然后我们也需要处理一下按钮触发的信息打印需要覆盖地图或者创建新窗口。将文本渲染在一个另外的地方,需要时单击按钮打开。
----------------------------------------------------------------------
我们的任务
1.读取文件到字符串流
2.设置按钮控制文本渲染(已实现),但是需要在新窗口中
3.使用线程打印17个决赛室的数据
4.打开项目中的文件(已经生成的秩序册)以供参赛队伍查询
如此一来,条件二也将圆满完成
-------------------------------------------------------------------------
然后去实现他们,先来个简单的
打开项目中的文件
首先将之前生成的秩序册文件拷贝一个放在项目的res文件中,然后在renderer中设置一个函数,用来打开文件。(或者使用绝对路径)
#include <windows.h> //MSVC用于打开窗口
#include <shellapi.h> //windows的shell_API
int OpenBook() {
// 指定要打开的文件路径
std::wstring filePath = L"YOUR FILEPATH";
// 使用ShellExecute函数打开文件
// 第一个参数是窗口的句柄,通常为NULL
// 第二个参数是操作,"open"表示打开文件
// 第三个参数是要打开的文件的路径
// 第四个参数是工作目录,通常为NULL
// 第五个参数是命令行参数,通常为NULL
// 第六个参数是窗口显示命令,通常为SW_SHOWNORMAL
HINSTANCE result = ShellExecute(NULL, L"open", filePath.c_str(), NULL, NULL, SW_SHOWNORMAL);
// 检查是否成功打开文件
if ((int)result <= 32) {
printf("Failed to open file.\n");
return 1;
}
}
注意
ShellExecute的参数需要接受长字符型,故使用了wstring,然后将参数中的open改为L"open"
这样一来就能实现文件的打开了:
读取文件以供渲染
因为等会决定使用线程,所以我决定将十七个分组分别读到十七个字符串流中。然后分配给十六个线程对象。
于是去设计一个函数。
先是设置了一个结构体,用来存储我们的数据,起到中转作用。
struct MESSAGE {
std::string mes_1;
std::string mes_2;
std::string mes_3;
std::string mes_4;
std::string mes_5;
std::string mes_6;
std::string mes_7;
std::string mes_8;
std::string mes_9;
std::string mes_10;
std::string mes_11;
std::string mes_12;
std::string mes_13;
std::string mes_14;
std::string mes_15;
std::string mes_16;
std::string mes_17;
};
然后嘞,写一个读取文件到字符串流中的函数
MESSAGE ReadInBook(const std::string& filepath)
{
enum class LOCATION {
NONE = -1, ZERO = 0, ONE = 1, TWO = 2, THREE = 3, FOUR = 4, FIVE = 5, SIX = 6, SEVEN = 7, EIGHT = 8, NINE = 9, TEN = 10, ELEVEN = 11, TWELVE = 12, THIRTEEN = 13, FOURTEEN = 14, FIFTEEN = 15, SIXTEEN = 16
};
std::ifstream stream(filepath);
std::string line;
std::stringstream ss[17];
LOCATION local = LOCATION::NONE;
while (getline(stream, line))
{
if (line[0] == '1' && line[1] == '\t') {
local = LOCATION::ZERO;
ss[(int)local] << line << '\n';
}
else if(line[0] == '2' && line[1] == '\t'){
local = LOCATION::ONE;
ss[(int)local] << line << '\n';
}
else if (line[0] == '3' && line[1] == '\t') {
local = LOCATION::TWO;
ss[(int)local] << line << '\n';
}
else if (line[0] == '4' && line[1] == '\t') {
local = LOCATION::THREE;
ss[(int)local] << line << '\n';
}
else if (line[0] == '5' && line[1] == '\t') {
local = LOCATION::FOUR;
ss[(int)local] << line << '\n';
}
else if (line[0] == '6' && line[1] == '\t') {
local = LOCATION::FIVE;
ss[(int)local] << line << '\n';
}
else if (line[0] == '7' && line[1] == '\t') {
local = LOCATION::SIX;
ss[(int)local] << line << '\n';
}
else if (line[0] == '8' && line[1] == '\t') {
local = LOCATION::SEVEN;
ss[(int)local] << line << '\n';
}
else if (line[0] == '9' && line[1] == '\t') {
local = LOCATION::EIGHT;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '0' && line[3] == '\t') {
local = LOCATION::NINE;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '1' && line[3] == '\t') {
local = LOCATION::TEN;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '2' && line[3] == '\t') {
local = LOCATION::ELEVEN;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '3' && line[3] == '\t') {
local = LOCATION::TWELVE;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '4' && line[3] == '\t') {
local = LOCATION::THIRTEEN;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '5' && line[3] == '\t') {
local = LOCATION::FOURTEEN;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '6' && line[3] == '\t') {
local = LOCATION::FIFTEEN;
ss[(int)local] << line << '\n';
}
else if (line[0] == '1' && line[2] == '7' && line[3] == '\t') {
local = LOCATION::SIXTEEN;
ss[(int)local] << line << '\n';
}
else {
if (local == LOCATION::NONE) continue;
}
}
return { ss[0].str(), ss[1].str(), ss[2].str(), ss[3].str(), ss[4].str(), ss[5].str(), ss[6].str(), ss[7].str(), ss[8].str(), ss[9].str(), ss[10].str(), ss[11].str(), ss[12].str(), ss[13].str(), ss[14].str(), ss[15].str(), ss[16].str(), };
}
其中使用枚举类作为下标标识,对ss进行输入。(由于没想到啥好方法,只能先手动将判断条件一个个敲出来 0.0)
为了方便使用,我们写一个自己的读出函数,将这个函数的结果返回成一个vector。
std::vector<std::string> ReadOutBook(MESSAGE& messages)
{
std::vector<std::string> mes;
for (const auto& member : { &messages.mes_1, &messages.mes_2, &messages.mes_3, &messages.mes_4, &messages.mes_5, &messages.mes_6, &messages.mes_7, &messages.mes_8, &messages.mes_9, &messages.mes_10, &messages.mes_11, &messages.mes_12, &messages.mes_13, &messages.mes_14, &messages.mes_15, &messages.mes_16, &messages.mes_17}) {
mes.push_back(*member);
}
return mes;
}
这样一来,我们既可以在主函数中
//read in & read out the order book
MESSAGE messages = ReadInBook("E:/VS/info_management/info_management/res/OrderBook.txt");
std::vector<std::string> mes = ReadOutBook(messages);
这样我们得到了一个包含数据的数组mes,而且mes[0]就是MESSAGE类中的mes_1
在新窗口中渲染
我想到一个更好的方法
//决赛信息墙纸绘制---------------------------------------------------
Matrix WWWprojection("mat4");
WWWprojection.Perspective(camera.m_Zoom, SCR_WIDTH, SCR_HEIGHT, 0.1f, 100.0f);
Matrix WWWview("mat4");
WWWview = camera.GetViewMatrix();
Matrix WWWmodel("mat4");
WWWmodel.Translate(glm::vec3(0.0f, 0.0f, 2.0f));
WWWmodel.Rotate(90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
GLCALL(glStencilMask(0x00));
planShader.Bind();
texMes.Bind();
planShader.SetUniformMatrix4fv("u_Projection", WWWprojection.GetMatrixData());
planShader.SetUniformMatrix4fv("u_View", WWWview.GetMatrixData());
planShader.SetUniformMatrix4fv("u_Model", WWWmodel.GetMatrixData());
renderer.DrawArrays(planeVa, 6, planShader);
(这里有本人自己定义的函数,大概思路就是创建投影矩阵,观察矩阵,模型矩阵,绑定着色器,绑定纹理,传入数据。然后通过调整在之前的地图面前远一点的地方绘制一个texMes(这个纹理是纯白色的),这相当于一张白色墙纸)然后我们得到:
使用线程同时渲染信息(同时将其调整到适合的位置)
为了使用线程,我需要编写一个函数,先声明一下
void Print(std::vector<std::string> mes, int id, Shader& shader, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color);
然后定义
void Print(std::vector<std::string> mes, int id, Shader& shader, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color)
{
using namespace std::literals::chrono_literals; //因为要使用this_literals::sleep_for
size_t pos = 0;
Font font("res/fonts/BRADHITC.TTF");
Matrix fontProjection("mat4");
fontProjection = glm::ortho(0.0f, static_cast<GLfloat>(SCR_WIDTH), 0.0f, static_cast<GLfloat>(SCR_HEIGHT)); //正交
shader.Bind();
shader.SetUniformMatrix4fv("u_Projection", fontProjection.GetMatrixData());
while ( (pos = mes[id].find('\n', pos)) != std::string::npos )
{
auto data = mes[id].substr(0, pos);
font.RenderText(shader, data, x, y, scale, color);
std::this_thread::sleep_for(2s);
pos++;
}
}
这里解释一下while中的条件
mes[id]是vector中存放的一个字符串类型变量
mes[id].find('\n', pos)的意思是在这个字符串中寻找'\n'换行符,从pos = 0开始
(pos = mes[id].find('\n', pos))是在寻找的同时将find()返回的值传递给pos
while ( (pos = mes[id].find('\n', pos)) != std::string::npos )是将pos与 std::string::npos进行比较,当二者不相等时,即找到'\n'这个元素
如果找到了
就将此行的数据从头到尾复制下来传递给auto data,然后用它来执行我的文本渲染操作
最后线程休眠一秒,pos++,变成下一行的第一个字符位置(下一行开头)
那么在主函数中呢,我们需要开始使用它。
首先定义一个线程对象
//add threads
std::thread Thread_1( Print, mes, 0, fontShader, 25.0f, 25.0f, 1.0f, glm::vec3(0.5, 0.8f, 0.2f));
这里注意不要写成std::thread Thread(func())或者std::thread Thread(func(parameter...))之类的,这是构造线程时错误的参数。
正确的方式是将自定义的函数名称作为线程构造函数的一个参数,而不是将你使用的函数填入参数放进去。
----------------------------------------------------------------------------------
调试了一下午,不知为什么总是会报错,在将fontshader这个参数注释掉之后(以及函数定义中将要使用fontshader的语句)我发现报错消失,故确定问题出在着色器身上,突发奇想试一下std::ref()没想到成了,至少没再继续报错。
std::thread Thread_2(Print, mes, 0, std::ref(fontShader), 50.0f, 2.0f, 1.0f, glm::vec3(0.5, 0.8f, 0.2f));
使用std::ref之前的报错:
这个问题是出在传参上面的,好像是因为在定义Print时候我的shader是以引用的方式传入的
可以参考这个帖子及回答,因为他和我一样错误情况
(C++ 使用多线程:错误C2672“invoke”: 未找到匹配的重载函数_编程语言-CSDN问答)
这个也可以看看
(多线程传参报错 :错误C2672 “std::invoke”: 未找到匹配的重载函数-CSDN博客)
所以就当我解决了这个问题 :)
---------------------------------------------------------------------------------------------------
这部分遇到一些不太了解的麻烦,总的来说是因为线程和渲染循环发生的冲突,特别是时间上的监视。
无论是join或detach都是要么因为渲染次数只调用一次,要么一瞬间调用很多次。
由于要使用std::this_thread::sleep_for(2s);来控制打印的信息更替,所以得放在渲染循环中,但是经常导致渲染停滞。
我看来,目前的项目与线程融合的并不很好。我决定弃用这个方法,使用更简便的方式完成同样的效果,我仅仅记录本地时间来判断渲染循环是否到达两秒,并为其更替打印信息,在打印信息时,或许我会考虑使用线程呢?
--------------------------------------------------------------------------------------------
设置一个监视渲染循环是否运行了两秒
void monitorRenderLoop(std::chrono::steady_clock::time_point& start_time) {
auto current_time = std::chrono::steady_clock::now();
auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - start_time).count();
if (elapsed_time >= 2000) {
std::cout << 'a' << '\n';
//update the mes that needs to print
start_time = std::chrono::steady_clock::now();
}
}
在app中,渲染循环外设定一个值,并在渲染中使用函数
auto start_time = std::chrono::steady_clock::now();
std::string printMes = "2s later draw the competition mes";
while(){
if (monitorRenderLoop(start_time)) {
printMes = updateMes(mes);
}
//render
//...
glfwSwapBuffers(window);
glfwPollEvents();
}
长长的一行,但是由于是中文所以乱码。