Rustでスレッドプールを使う場合、threadpoolクレートが有名らしい。
https://crates.io/crates/threadpool
use threadpool::ThreadPool; fn main() { // スレッドを四枚作成 let pool = threadpool::ThreadPool::new(4); // テスト用データ let myarray = std::sync::Arc::new(std::sync::Mutex::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); for i in 0..10{ // myarrayは参照なので、cloneして参照を複製する // クロージャに渡すmoveキーワードによって、myarray2がクロージャにムーブされる let array2 = myarray.clone(); // スレッドプールにジョブを追加 pool.execute(move || {
let mut data = array2.lock().unwrap(); data[i] *= 2;
} ); } // スレッドの終了を待つ pool.join(); // 結果を表示 let data = myarray.lock().unwrap(); println!("{:?}", data); }
channel()を使って各ジョブの結果を送信できる。
use threadpool::ThreadPool; // チャンネルを使うために必要 use std::sync::mpsc::channel; fn main() { // スレッドを四枚作成 let pool = threadpool::ThreadPool::new(4); // スレッドで処理した結果を受け取るためのチャンネルを作成 let (sender,receiver) = channel::<i32>(); // テスト用データ let myarray = std::sync::Arc::new(std::sync::Mutex::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); for i in 0..10{ // myarrayは参照なので、cloneして参照を複製する // クロージャに渡すmoveキーワードによって、myarray2がクロージャにムーブされる let array2 = myarray.clone(); let sender2 = sender.clone(); // スレッドプールにジョブを追加 pool.execute(move || { let mut data = array2.lock().unwrap(); data[i] *= 2; sender2.send(data[i]*10).unwrap(); // このジョブの結果をチャンネルに送信 } ); } // スレッドの終了を待つ pool.join(); // 全ての処理が終わったら、チャンネルを閉じる drop(sender); // チャンネルから結果を受け取る for itr in receiver.iter(){ print!("{} ", &itr); } }
Daz to Blender BridgeでBlenderへエクスポート可能だが、ポーズを正確に移行できないので、まずフィギュアだけをエクスポートし、Blender側でdufファイルをインポートする。
Daz Studio 4.21で、[File] → [Send To] → [Daz to Blender]でエクスポートが可能だが、Blender側の準備が必要になる。一応使い方がダイアログ内に書いてある。
ここで、使用するBlenderのバージョンを指定し、 Install Pluginをクリックする。
Blender側では、DazToBlenderアドオンがリストにあるのでPreferenceから有効にする
ポーズをプリセットから選択し、そのポーズをBlenderへエクスポートすることを考える。
ポイントはフィギュアだけをエクスポートした後でポーズを適用すると成功する。
まずフィギュアを読み込み、服を着せ髪を乗せる。
次に、[File] → [Send To] → [Daz to Blender]を開き、Acceptする。
注意点として、フィギュアの頭だけ、髪だけという選択状態だと失敗する可能性があるので、何も選択しないか、フィギュア本体だけを選択しておく必要がある。
Blender側で、Daz To Blenderを有効にしていれば、画面右側にタブが増えているので、それを表示し、[Import New Genesis Figure]をクリックする
Daz側に戻り、ポーズのプリセット一覧を表示する。使用したいポーズを見つけたら右クリックし、[Browse to File Location...]を選択、エクスプローラで表示する。
該当ポーズがあるパスとファイル名がわかったら、Blender側に戻る。
[Import Pose]をクリックし、ファイル読み込みダイアログを開く。先ほど調べたパスへ移動し、ファイル名を探して選択、[Import Pose]をクリックする。
レンダリングしてみる。概ねフィットしているが服が飛び出している部分がある。
println!のように、関数のように呼び出すマクロをfunction-likeマクロというらしい。
マクロを定義するとき、
例えばこんな感じ。
macro_rules! マクロ名 { ($引数:expr) => { println!("引数を使う: {}", $引数); }; }
使用例。
macro_rules! print_arg { ($arg:expr) => { println!("Argument: {}", $arg); }; }
fn main(){ print_arg!(3.5); // 出力: Argument: 3.5 }
macro_rules! print_arg {
($e1:expr, $e2:expr) =>{
println!("e1: {} e2 {}", $e1,$e2);
};
} fn main(){ print_arg!(3.5 , "hello"); }
macro_rules! print_arg { ( $($arg:expr) , * ) => { $( println!("Argument: {}", $arg); ) * }; } fn main(){ print_arg!(3.5 , "hello" , 6); // Argument: 3.5 // Argument: hello // Argument: 6 }
trait MyPrint{ fn print(&self); }
impl MyPrint for i32{ fn print(&self){ println!("i32: {}", *self); } } impl MyPrint for f32{ fn print(&self){ println!("f32: {}", *self); } }
macro_rules! print_arg { ($e:expr) =>{ MyPrint::print(&$e); }; }
fn main(){ print_arg!(3.5); // f32: 3.5 print_arg!(100); // i32: 100 }
Index Buffer使用時には、GL_ELEMENT_ARRAY_BUFFERをBindした後glDrawElementsを使用。
glEnableClientStateで特別な指定をする必要はない。
// バッファ名 GLuint buffer[3]; const int VTX = 0; const int COL = 1; const int IDX = 2;
void init_data() { float v = 0.7; // 頂点データ std::vector< std::array<float,3> > vertex = { { 0.0f, 0.0f ,0.0f}, // 中心 (0) { 1.0f, 0.0f ,0.0f}, // 右中 (1) { 0.5f, 0.87f,0.0f}, // 右上 (2) {-0.5f, 0.87f,0.0f}, // 左上 (3) {-1.0f, 0.0f ,0.0f}, // 左中 (4) {-0.5f, -0.87f,0.0f}, // 左下 (5) { 0.5f, -0.87f,0.0f} // 右下 (6) }; // 色データ std::vector< std::array<float, 3> > color = { {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 1.0f}, {0.5f, 0.5f, 0.5f} }; // インデックスデータ std::vector< std::array<GLuint, 3> > index = { { 0, 1, 2 }, { 0, 2, 3 }, { 0, 3, 4 }, { 0, 4, 5 }, { 0, 5, 6 }, { 0, 6, 1 } }; // バッファ作成 glGenBuffers(3, &buffer[0]); /////////////////////////////////////////// // 頂点バッファの作成 glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * vertex.size(), vertex[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// // 色バッファの作成 glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * color.size(), color[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// // インデックスバッファの作成 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[IDX]); glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * 3 * index.size(), index[0].data(), GL_STATIC_DRAW ); }
void render_data() { glEnableClientState(GL_COLOR_ARRAY); // 色配列を有効化 glEnableClientState(GL_VERTEX_ARRAY); // 頂点配列を有効化 // 頂点配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glVertexPointer(3, GL_FLOAT, 0, 0); // 色配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glColorPointer(4, GL_FLOAT, 0, 0); // インデックス配列を有効化 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[IDX]); // 描画 glDrawElements(GL_TRIANGLES, 6*3/*6頂点。各頂点3要素*/, GL_UNSIGNED_INT, 0); // 頂点配列を無効化 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); }
void finalizer() { glDeleteBuffers(3, buffer); }
Vertex Buffer Objectが導入されたのはOpenGL 1.5からで、この時はシェーダの指定が必要なかったとのこと。別にシェーダを用意するのもテストでは面倒なのでありがたい。
glEnableClientStateで使用するバッファの種類を指定。glBindBufferした後glVertexPointerする。
glVertexPointerの最後の「0」はCPUメモリ上のバッファのポインタを入れるが、0を指定すると使用する頂点バッファがバインドしたバッファになる。
#include <Windows.h> #include <GL/glew.h> #include <gl/GL.h> #include <gl/GLU.h> #include <gl/freeglut.h> #include <vector> #include <array> #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glew32.lib") //ウィンドウの幅と高さ int width, height; double rotate_angle = 0; // バッファ名 GLuint buffer;
void init_data() { glEnableClientState(GL_VERTEX_ARRAY); float v = 0.7; std::vector< std::array<float,3> > data; data.push_back({ 0.f,0.f,0.f }); data.push_back({ -v,-v,0 }); data.push_back({ v,-v,0 }); glGenBuffers(1, &buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * data.size(), data[0].data(), GL_STATIC_DRAW); glVertexPointer(3, GL_FLOAT, 0, 0); }
void render_data() { glColor3f(1, 0, 0); // バッファのデータと関連づけ glBindBuffer(GL_ARRAY_BUFFER, buffer); glVertexPointer(3, GL_FLOAT, 0, 0); // 描画 glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); }
//描画関数 void disp(void) { glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, width, height); //カメラの設定 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60, width / (double)height, 0.1, 3); glMatrixMode(GL_MODELVIEW);//モデルビューの設定 glLoadIdentity(); glTranslated(0, 0, -2); glRotated(rotate_angle, 0, 1, 0); //////////////////////////////////////////// render_data(); //////////////////////////////////////////// glFlush(); }
void finalizer() { glDeleteBuffers(1, &buffer); }
//ウィンドウサイズの変化時に呼び出される void reshape(int w, int h) { width = w; height = h; disp(); } void timer(int value) { rotate_angle += 5; disp(); glutTimerFunc(10, timer, 0); } //エントリポイント int main(int argc, char** argv) { glutInit(&argc, argv); glutInitWindowPosition(100, 50); glutInitWindowSize(500, 500); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA); glutCreateWindow("sample"); glewInit(); init_data(); glutDisplayFunc(disp); glutReshapeFunc(reshape); glutTimerFunc(1000, timer, 0);//タイマー glutMainLoop();
finalizer();
return 0; }
glBindBuffer → glVertexPointer で、バインドしたバッファを頂点座標に使用することを教える。
glBindBuffer → glColorPointer で、バインドしたバッファを頂点色に使用することを教える。
#include <Windows.h> #include <GL/glew.h> #include <gl/GL.h> #include <gl/GLU.h> #include <gl/freeglut.h> #include <vector> #include <array> #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glew32.lib") //ウィンドウの幅と高さ int width, height; double rotate_angle = 0; // バッファ名 GLuint buffer[2]; const int VTX = 0; const int COL = 1;
void init_data() { float v = 0.7; // 頂点データ std::vector< std::array<float,3> > vertex; vertex.push_back({ 0,0,0 }); vertex.push_back({ -v,-v,0 }); vertex.push_back({ v,-v,0 }); // 色データ std::vector< std::array<float, 4> > color; color.push_back({ 1,0,0,1 }); color.push_back({ 0,1,0,1 }); color.push_back({ 0,0,1,1 }); // バッファ作成 glGenBuffers(2, &buffer[0]); /////////////////////////////////////////// glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * vertex.size(), vertex[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glBufferData( GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * color.size(), color[0].data(), GL_STATIC_DRAW); /////////////////////////////////////////// }
void render_data() { glEnableClientState(GL_COLOR_ARRAY); // 色配列を有効化 glEnableClientState(GL_VERTEX_ARRAY); // 頂点配列を有効化 // 頂点配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[VTX]); glVertexPointer(3, GL_FLOAT, 0, 0); // 色配列を有効化 glBindBuffer(GL_ARRAY_BUFFER, buffer[COL]); glColorPointer(4, GL_FLOAT, 0, 0); // 有効化の設定ここまで glBindBuffer(GL_ARRAY_BUFFER, 0); // 描画 glDrawArrays(GL_TRIANGLES, 0, 3); }
//描画関数 void disp(void) { glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, width, height); //カメラの設定 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60, width / (double)height, 0.1, 3); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslated(0, 0, -2); glRotated(rotate_angle, 0, 1, 0); //////////////////////////////////////////// render_data(); //////////////////////////////////////////// glFlush(); }
void finalizer() { glDeleteBuffers(2, buffer); }
実行するとgluPerspectiveやglTranslated,glRotatedでの指定で三角形が回転する。
算譜記録帳
http://mklearning.blogspot.com/2014/08/opengl.html
床井研究
前回矩形を分割するプログラムを書いたのだが、親要素に対する割合でしか指定するように作っていなかったことに気づいた。なのでピクセル単位での指定もできるようにした。
#include <windows.h> #include <iostream> #include <string> #include <vector> #include <pugixml.hpp> #include <functional> // 領域の計算方法 enum class SizeType { RATIO, // 親要素に対する割合で指定 PIXEL, // ピクセル数で指定 AUTO, // 余っているサイズから自動計算 }; // 子要素の配置方法 enum class Arrangement { Horizonal, Vertical, }; // 各情報を元に計算される実際のサイズ。sx,sy, ex,eyは矩形の最小、最大を表すピクセル座標 struct Rect { int sx, sy; int ex, ey; }; struct BOX { SizeType sizeType; // この領域のサイズの指定方法 Arrangement type; // 子要素の配置方法 Rect rect; // この領域の矩形 std::string name; // この領域の名前 // ratioとpixelはどちらか一方が指定される double ratio; // この領域が親要素のどれだけの割合を占めるか int pixel; // この領域が親要素のどれだけのピクセルを占めるか std::vector<BOX> children; // 子要素 };
// xmlファイルの内容を解析してノード作成 void parseNode(const pugi::xml_node& xmlNode, BOX& node) { // BOXの名前を取得 node.name = xmlNode.attribute("name").as_string(""); // BOXの親要素に対する割合を取得。指定がない場合はNaNにしておいて、自動計算する node.ratio = xmlNode.attribute("ratio").as_double( std::numeric_limits<double>::quiet_NaN() ); node.pixel = xmlNode.attribute("pixel").as_int(-1); const auto& attr_ratio = xmlNode.attribute("ratio"); const auto& attr_pixel = xmlNode.attribute("pixel"); // "ratio" および "pixel" が存在しない場合はAUTO if (attr_ratio.empty() && attr_pixel.empty() ) { node.sizeType = SizeType::AUTO; } // "ratio" が存在し、かつ内容が"auto"であればAUTO else if (!attr_ratio.empty() && std::string(attr_ratio.as_string()) == "auto") { node.sizeType = SizeType::AUTO; } // "pixel" が存在し、かつ内容が"auto"であればAUTO else if (!attr_ratio.empty() && std::string(attr_pixel.as_string()) == "auto") { node.sizeType = SizeType::AUTO; } else if (!attr_ratio.empty()) { node.sizeType = SizeType::RATIO; } else if (!attr_pixel.empty()) { node.sizeType = SizeType::PIXEL; } else { node.sizeType = SizeType::AUTO; } // 配置方法を取得。デフォルトをverticalとしておく。 std::string arrangement = xmlNode.attribute("arrangement").as_string("vertical"); node.type = (arrangement == "horizonal") ? Arrangement::Horizonal : Arrangement::Vertical; // 子要素を再帰的に解析 for (pugi::xml_node child = xmlNode.first_child(); child; child = child.next_sibling()) { BOX childNode; parseNode(child, childNode); node.children.push_back(childNode); } }
// xmlファイルからレイアウト情報を読み込む BOX LoadLayout(const char* xmlfilename) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(xmlfilename); // ファイルが読み込めなかったら例外を投げる if (!result) { throw std::runtime_error("xmlファイルが読み込めませんでした"); } BOX rootNode; pugi::xml_node root = doc.child("box"); parseNode(root, rootNode); return rootNode; }
// UpdateLayout関数 void UpdateLayout(BOX& node, const Rect& parent) { node.rect = parent; int totalWidth = parent.ex - parent.sx; int totalHeight = parent.ey - parent.sy; int total; if (node.type == Arrangement::Horizonal) { // 水平方向に並べる場合 total = totalWidth; } else { total = totalHeight; } ///////////////////////////////// // 親要素を占める割合の計算 // ratioを省略可能としている // ratioが省略されたときは、その領域内の残りのratioを省略されたbox数で割る int undefinedRatioCount = 0; // ratioが定義されていないboxの個数 size_t last_item_index; // まず、指示のある領域のサイズを計算する // タイプがpixelの場合は、そのままpixelを使う // タイプがratioの場合は、親要素の割合を使い、ピクセルに変換する // タイプが指定されていない場合は、その数をカウントし、childを記憶しておく // totalから確定したサイズを引いた残りをピクセルで計算 int remainingPixel = total; std::vector<int> sizes(node.children.size()); for (size_t child_id = 0; child_id < node.children.size(); child_id++) { switch (node.children[child_id].sizeType) { case SizeType::PIXEL: // pixelが指定されている場合はそのまま使う sizes[child_id] = node.children[child_id].pixel; remainingPixel -= node.children[child_id].pixel; // 残りのピクセル数を計算 break; case SizeType::RATIO: sizes[child_id] = node.children[child_id].ratio * total; remainingPixel -= sizes[child_id]; // 残りのピクセル数を計算 break; case SizeType::AUTO: sizes[child_id] = -1; // 未定義の場合は-1にしておく undefinedRatioCount++; last_item_index = child_id; break; } } // ratioが定義されていないノードがある場合は、使用可能な割合を等分する if (undefinedRatioCount > 0) { // 未定義のノードに割り当てる割合を計算 // 残っている使えるピクセル数 / サイズ未定義のノード数 const double equaldivision = (double)remainingPixel / (double)undefinedRatioCount; int sizesum = 0; // 全てのノードに割り当てた場合のピクセル数を計算 for (size_t child_id = 0; child_id < node.children.size(); child_id++) { if (sizes[child_id] == -1) { sizesum += equaldivision; } else { sizesum += sizes[child_id]; } } // 最後のノードで誤差を吸収 // 分割したピクセル数の合計が親要素のピクセル数より大きければマイナスになる int lastitem_error = total - sizesum; // 実際にサイズを与える for (size_t child_id = 0; child_id < node.children.size(); child_id++) { if (sizes[child_id] == -1) { sizes[child_id] = equaldivision; } } sizes[last_item_index] += lastitem_error; } // 計算した長さを子要素の矩形に与える int offset=0; for (size_t child_id = 0; child_id < node.children.size(); child_id++) { auto& child = node.children[child_id]; if (child_id == 0) { offset=0; } else { offset += sizes[child_id-1]; } if (node.type == Arrangement::Horizonal) { child.rect.sx = parent.sx + offset; child.rect.ex = child.rect.sx + sizes[child_id]; child.rect.sy = parent.sy; child.rect.ey = parent.ey; } else { child.rect.sy = parent.sy + offset; child.rect.ey = child.rect.sy + sizes[child_id]; child.rect.sx = parent.sx; child.rect.ex = parent.ex; } } // 子要素の子要素の矩形を計算する for (auto& child : node.children) { UpdateLayout(child, child.rect); } }
///////////////////////////////////////// ///////////////////////////////////////// ///////////////////////////////////////// /////////////////////////////////////////
// 領域を描画 void drawRects(HDC hdc, const BOX& node) { RECT rect = { node.rect.sx, node.rect.sy, node.rect.ex, node.rect.ey }; // ランダムな色を作成 int r = rand() % 256; int g = rand() % 256; int b = rand() % 256; if (node.type == Arrangement::Horizonal) { HPEN hPen = CreatePen(PS_SOLID, 1, RGB(r, g, b)); // 1ピクセル幅のペンを作成 HGDIOBJ oldPen = SelectObject(hdc, hPen); // ペンを選択 // 矩形を描画 Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); SelectObject(hdc, oldPen); // 元のペンに戻す DeleteObject(hPen); // ペンを削除 } else { // 塗りつぶした矩形 HBRUSH hBrush = CreateSolidBrush(RGB(r, g, b)); // ブラシを作成 HGDIOBJ oldBrush = SelectObject(hdc, hBrush); // ブラシ Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); SelectObject(hdc, oldBrush); // 元のブラシに戻す DeleteObject(hBrush); // ブラシを削除 } for (const BOX& child : node.children) { drawRects(hdc, child); } }
BOX rootArea; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (msg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_CREATE: GetClientRect(hwnd, &rect); UpdateLayout(rootArea, Rect{ 0, 0, rect.right, rect.bottom }); InvalidateRect(hwnd, nullptr, TRUE); return 0; case WM_SIZING: GetClientRect(hwnd, &rect); UpdateLayout(rootArea, Rect{ 0, 0, rect.right, rect.bottom }); InvalidateRect(hwnd,nullptr,TRUE); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); drawRects(hdc, rootArea); EndPaint(hwnd, &ps); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND hwnd; WNDCLASS winc; MSG msg; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("SZL-WINDOW"); if (!RegisterClass(&winc)) return 0; Rect rootRect{ 0, 0, 800, 300 }; rootArea = LoadLayout(R"(C:\test\layout3.xml)"); UpdateLayout(rootArea, rootRect); hwnd = CreateWindow( TEXT("SZL-WINDOW"), TEXT("test window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 800, 500, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; }
<?xml version="1.0" encoding="utf8" ?> <box name="main" ratio="1.0" arrangement="horizonal"> <box name="menu" pixel="300" arrangement="vertical"> <box name="menu-top" pixel="50" arrangement="horizonal"> <box name="area-a" pixel="50" arrangement="vertical"></box> <box name="area-b" pixel="50" arrangement="vertical"></box> <box name="area-c" ratio="auto" arrangement="vertical"></box> </box> <box name="menu-middle" ratio="0.2" arrangement="horizonal"></box> <box name="menu-bottom" arrangement="horizonal"></box> </box> <box name="containts" ratio="auto" arrangement="vertical" > </box> </box>
UIを簡易的に定義したいので、自作xmlとそれに基づいて領域を計算するコードを書く。
まずXMLを定義。
nameは識別子だが一意である必要はない。arrangementは子要素の配置方法。ratioは親要素に対するその要素の割合。
<?xml version="1.0" encoding="utf8" ?> <box name="main" ratio="1.0" arrangement="horizonal"> <box name="red" ratio="0.3" arrangement="vertical"> <box name="area1"></box> <box name="area2"></box> <box name="area3" ratio="0.5"></box> </box> <box name="blue" ratio="0.6" arrangement="vertical" > <box name="area1" ratio="0.2"></box> <box name="area2" ratio="0.8"></box> </box> <box name="green" ratio="0.1" arrangement="vertical" > <box name="area1" ratio="0.3"></box> <box name="area2" ratio="0.3"></box> <box name="area3" ratio="0.3"></box> <box name="area4" ratio="0.1" arrangement="horizonal"> <box name="target1" ratio="0.3"></box> <box name="target2" ></box> </box> </box> </box>
#include <windows.h> #include <iostream> #include <string> #include <vector> #include <pugixml.hpp> #include <functional> // 子要素の配置方法 enum class Arrangement { Horizonal, Vertical, }; // 各情報を元に計算される実際のサイズ。sx,sy, ex,eyは矩形の最小、最大を表すピクセル座標 struct Rect { int sx, sy; int ex, ey; }; struct BOX { Arrangement type; // 子要素の配置方法 Rect rect; // この領域の矩形 std::string name; // この領域の名前 double ratio; // この領域が親要素のどれだけの割合を占めるか std::vector<BOX> children; // 子要素 };
// xmlファイルの内容を解析してノード作成 void parseNode(const pugi::xml_node& xmlNode, BOX& node) { // BOXの名前を取得 node.name = xmlNode.attribute("name").as_string(""); // BOXの親要素に対する割合を取得。指定がない場合はNaNにしておいて、自動計算する node.ratio = xmlNode.attribute("ratio").as_double( std::numeric_limits<double>::quiet_NaN() ); // 配置方法を取得。デフォルトをverticalとしておく。 std::string arrangement = xmlNode.attribute("arrangement").as_string("vertical"); node.type = (arrangement == "horizonal") ? Arrangement::Horizonal : Arrangement::Vertical; // 子要素を再帰的に解析 for (pugi::xml_node child = xmlNode.first_child(); child; child = child.next_sibling()) { BOX childNode; parseNode(child, childNode); node.children.push_back(childNode); } }
// xmlファイルからレイアウト情報を読み込む BOX LoadLayout(const char* xmlfilename) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(xmlfilename); // ファイルが読み込めなかったら例外を投げる if (!result) { throw std::runtime_error("xmlファイルが読み込めませんでした"); } BOX rootNode; pugi::xml_node root = doc.child("box"); parseNode(root, rootNode); return rootNode; }
// 読み込んだxmlファイルから領域を計算 void UpdateLayout(BOX& node, const Rect& parent) { node.rect = parent; int totalWidth = parent.ex - parent.sx; int totalHeight = parent.ey - parent.sy; ///////////////////////////////// // 親要素を占める割合の計算 // ratioを省略可能としている // ratioが省略されたときは、その領域内の残りのratioを省略されたbox数で割る double remainingRatio = 1.0; // 1.0 = 100% parent全体の割合を1.0とする int undefinedRatioCount = 0; // ratioが定義されていないboxの個数 for (auto& child : node.children) { if (std::isnan(child.ratio) == true) { // ratioが定義されていないノードの個数を知りたいのでここで確認する undefinedRatioCount++; } else { // ratioが定義されているノードの割合を引き、 // ratio未定義のboxが使用可能な割合を計算する remainingRatio -= child.ratio; } } // ratioが定義されていないノードがある場合は、使用可能な割合を等分する if (undefinedRatioCount > 0) { // ratioが定義されていないノードは、使用可能な量を等分する double defaultRatio = remainingRatio / undefinedRatioCount; // ratio未定義のノードにratioを与える for (auto& child : node.children) { if (std::isnan(child.ratio) == true) { child.ratio = defaultRatio; } } } // 子要素の矩形を計算する double accumRatio = 0.0; for (auto& child : node.children) { if (node.type == Arrangement::Horizonal) { child.rect.sx = parent.sx + totalWidth * accumRatio; child.rect.ex = child.rect.sx + totalWidth * child.ratio; child.rect.sy = parent.sy; child.rect.ey = parent.ey; } else { child.rect.sy = parent.sy + totalHeight * accumRatio; child.rect.ey = child.rect.sy + totalHeight * child.ratio; child.rect.sx = parent.sx; child.rect.ex = parent.ex; } accumRatio += child.ratio; } // 子要素の子要素の矩形を計算する for (auto& child : node.children) { UpdateLayout(child, child.rect); } }
// 領域を描画 void drawRects(HDC hdc, const BOX& node) { RECT rect = { node.rect.sx, node.rect.sy, node.rect.ex, node.rect.ey }; // ランダムな色を作成 int r = rand() % 256; int g = rand() % 256; int b = rand() % 256; HPEN hPen = CreatePen(PS_SOLID, 1, RGB(r, g, b)); // 1ピクセル幅のペンを作成 HGDIOBJ oldPen = SelectObject(hdc, hPen); // ペンを選択 // 矩形を描画 Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom); SelectObject(hdc, oldPen); // 元のペンに戻す DeleteObject(hPen); // ペンを削除 for (const BOX& child : node.children) { drawRects(hdc, child); } } BOX rootArea; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (msg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); drawRects(hdc, rootArea);// テスト用 矩形描画 EndPaint(hwnd, &ps); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND hwnd; WNDCLASS winc; MSG msg; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("SZL-WINDOW"); if (!RegisterClass(&winc)) return 0; Rect rootRect{ 0, 0, 800, 300 }; rootArea = LoadLayout(R"(layout2.xml)");// レイアウト読み込み UpdateLayout(rootArea, rootRect);// 矩形を計算 hwnd = CreateWindow( TEXT("SZL-WINDOW"), TEXT("test window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); return msg.wParam; }
Rustでスレッドを作成するにはspawnを使用する。よくあるように、一度作成したスレッドはハンドルを取得し、join()で待機する。スレッドとして走らせた関数の戻り値はjoin()の戻り値に入っているが、これはエラーなども扱うResultとして入っているのでunwrapして戻り値を取り出す。
spawnには原則として引数なしの関数しか渡せない。
// スレッドとして実行する関数 // 引数をとることはできないが、任意の型の戻り値を返すことはできる fn my_sum()->i32 { let mut sum = 0; for i in 0..50 { println!("{}", i); sum += i; } return sum; }
fn main(){ // 関数が引数を一つも取らない場合、spawnに関数名を渡すだけでスレッドが作成される let thread_handle1 = std::thread::spawn(my_sum); let thread_handle2 = std::thread::spawn(my_sum); // my_sumは引数をとることはできないが、任意の型の戻り値を返すことはできる let result1:i32 = thread_handle1.join().unwrap(); let result2:i32 = thread_handle2.join().unwrap(); println!("results are {} , {} ", result1,result2); }
引数を与える場合、直接spawnに与えることができないので、クロージャ(ラムダ関数のようなもの)を与える。Rustのクロージャは頭に || をつける。
以下の例では、「myprintに'a' 'b' 50 というマジックナンバーを与えて呼び出す」という処理を行う引数なしの関数をspawnに渡している。
// 値を表示する関数 fn myprint(mark : char,count:i32) { for i in 0..count{ println!("{} -> {}",mark,i); } }
fn main() { // spawnにはクロージャ(≒ラムダ関数)を渡す。 || はクロージャを表す。 // 即ち、「myprintを引数付きで渡す」という処理を行う、引数なしの関数を渡している。 let thread_handle1 = std::thread::spawn(|| myprint( 'a',50)); let thread_handle2 = std::thread::spawn(|| myprint( 'b',50)); // 終了まで待機 thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); }
メインスレッドのデータをスレッドの中で処理したい場合、メインスレッド側でデータが消えたりするとまずいので、そのまま参照を渡すことができない。
そこでデータを参照カウンタ付きのスマートポインタArcで渡すことで、メインスレッドを含め各スレッドが処理している最中にデータが消える危険性を排除する。
// vecの不変参照を受け取り、表示する関数 fn myprint(input: &Vec<i32>) { for i in input.iter(){ println!("i=={}",i); } }
// テスト用のVecのデータを作成する関数 fn create_data()->Vec<i32>{ let mut data = vec![]; for k in 0..100{ data.push(k); } return data; }
fn main() { // スレッドに渡すためにはArcでラップする必要がある let data1 = std::sync::Arc::new(create_data()); let data2 = std::sync::Arc::new(create_data()); // spawnのmoveキーワードによりスレッドに変数の所有権が移ってしまうので // clone()で参照のコピーを作成する。Arcのcloneは参照カウンタが増えるだけなので負荷はほぼない let d1_clone = data1.clone(); let d2_clone = data2.clone(); let thread_handle1 = std::thread::spawn(move || myprint( &d1_clone)); // d1_cloneの所有権が移動 let thread_handle2 = std::thread::spawn(move || myprint( &d2_clone)); // d2_cloneの所有権が移動 // 終了まで待機 thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); // データの所有権は移行していないので、ここで表示できる println!("data: {:?}", data1); println!("data: {:?}", data2); }
スレッド内で変数を編集したい場合、さらにMutexでラップする。
// 配列を受け取り、二倍にする関数 fn double(input: &std::sync::Mutex<Vec<i32>>) { let mut vecdata = input.lock().unwrap(); for i in vecdata.iter_mut() { *i *= 2; } }
fn main() { let data1 = std::sync::Arc::new(std::sync::Mutex::new(vec![1,2,3,4,5])); let data2 = std::sync::Arc::new(std::sync::Mutex::new(vec![10,20,30,40,50])); let mut d1_clone = data1.clone(); let mut d2_clone = data2.clone(); let thread_handle1 = std::thread::spawn(move || double(&d1_clone)); let thread_handle2 = std::thread::spawn(move || double(&d2_clone)); thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); println!("data1: {:?}", &data1); println!("data2: {:?}", &data2); }
C言語のタイマーと違い、精度の高いタイマーが標準で使用できる。ただし精度は環境依存なので、比較的新しい環境だとマイクロ秒,ナノ秒単位で取得できるが、そうでない場合も考えられる。
fn main() { // 時間計測開始 let mytimer = std::time::Instant::now(); use_rand(); // 時間計測終了 let elapsed = mytimer.elapsed(); // 経過時間を表示 println!("{:?}",elapsed); }
fn use_rand(){ use rand::Rng; // 乱数を使うためにrand::Rngを使用 let mut rng = rand::thread_rng(); let min:i32 = 0; let max:i32 = 20; (0..5000).for_each(|_|{ let randint:i32 = rng.gen_range(min..max); // 最大・最小を指定して乱数を生成 println!("{}",randint); }); }
CのclockなどはCPU時間なので、Sleep時間がタイマーに反映されないが、Rustのstd::timeはちゃんとSleep時間も含んだ結果が得られる。
fn main() { // 時間計測開始 let mytimer = std::time::Instant::now(); // 5秒間スリープ std::thread::sleep(std::time::Duration::from_secs(5)); // 時間計測終了 let elapsed = mytimer.elapsed(); // 経過時間を表示 println!("{:?}",elapsed); }
結果
表示だけならDuration型に対して{:?}すればよいが、値を取り出す場合は以下のようにする。
// 経過時間を表示 println!("second i32 {}",elapsed.as_secs()); println!("second f32 {}",elapsed.as_secs_f32()); println!("micro second {}",elapsed.as_micros()); println!("nano second {}",elapsed.as_nanos());
結果
second i32 5 second f32 5.000559 micro second 5000558 nano second 5000558900
Rustのスライスを使ってみたくて書いた。
クイックソートを書いたのはたぶん15年ぶりくらい。正直てこずった。
fn my_qsort_slice(data: &mut [i32]){ if data.len() <= 1{ return; } // ピボットを求める let pivot =data[data.len()/2]; let idmin:i32 = 0; let idmax:i32 = (data.len()-1) as i32; ////////////////////////////////////////////////// // ピボットより小さいものを左に、大きいものを右に分ける let mut l =idmin; // 左側の探索位置。pivotより大きい値を探す let mut r =idmax; // 右側の探索位置。pivot未満の値を探す loop{ // 左側の交換すべき値を探す while data[l as usize] < pivot { l += 1; } // 右側の交換すべき値を探す while data[r as usize] > pivot { r -= 1; } if l >= r{ break; } // 交換 let tmp = data[l as usize]; data[l as usize] = data[r as usize]; data[r as usize] = tmp;
l += 1;
r -= 1; } { // 左側のスライスを作成 let mut left = &mut data[0..l as usize]; // 左側のスライスを再帰的にソート my_qsort_slice(&mut left); } { // 右側のスライスを作成 let mut right = &mut data[ (r+1) as usize ..]; // 右側のスライスを再帰的にソート my_qsort_slice(&mut right); } }
fn my_qsort(data : &mut Vec<i32>){ my_qsort_slice(&mut data[..]); }
fn main() { let mut data:Vec<i32> = Vec::new(); data = [8,1,9,2,4,6].to_vec(); my_qsort(&mut data); for i in 0..data.len(){ println!("{}",data[i]); } println!("Hello, world!"); }