2087 lines
95 KiB
HTML
2087 lines
95 KiB
HTML
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<style>
|
||
/* ============================== *\
|
||
page base
|
||
\* ============================== */
|
||
|
||
body {
|
||
background-color: #F0F0F0;
|
||
margin: 0px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.page-base {
|
||
position: absolute;
|
||
}
|
||
|
||
#splitter {
|
||
width: 5px;
|
||
cursor: col-resize;
|
||
box-shadow: 0 0 4px #B0B0B0;
|
||
}
|
||
|
||
.page-container {
|
||
margin: 7.5px;
|
||
background-color: white;
|
||
overflow: auto;
|
||
}
|
||
#page-select-container {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
#page-display-container-body {
|
||
padding: 7px;
|
||
width: fit-content;
|
||
}
|
||
|
||
/* ============================== *\
|
||
page select
|
||
\* ============================== */
|
||
|
||
.arrow-page-root {
|
||
cursor: pointer;
|
||
}
|
||
.dot-page-root {
|
||
cursor: default;
|
||
margin-left: 5px;
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.ps-root-container {
|
||
margin: 7px;
|
||
}
|
||
|
||
.ps-folder {
|
||
display: table;
|
||
}
|
||
.ps-folder-name {
|
||
cursor: pointer;
|
||
padding: 1;
|
||
}
|
||
.ps-container {
|
||
margin-left: 7;
|
||
padding-left: 7;
|
||
border-left: thin solid;
|
||
border-color: #80808080;
|
||
}
|
||
|
||
.ps-page {
|
||
}
|
||
.ps-page-name {
|
||
cursor: pointer;
|
||
padding: 1;
|
||
}
|
||
|
||
/* ============================== *\
|
||
page display
|
||
\* ============================== */
|
||
|
||
blockquote > *:first-child {
|
||
margin-top: 0;
|
||
}
|
||
blockquote > *:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
blockquote {
|
||
margin-left: 0;
|
||
margin-right: 0;
|
||
padding: 12px;
|
||
border-left: 5px solid #d0d0d0;
|
||
background-color: #f7f7f7;
|
||
}
|
||
|
||
.smart-link {
|
||
color: blue;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.spoiler {
|
||
margin-left: 8px;
|
||
}
|
||
.spoiler-wrap {
|
||
border: solid;
|
||
border-width: 0px;
|
||
padding-left: 2px;
|
||
}
|
||
.spoiler-name {
|
||
border: solid;
|
||
border-width: 0px;
|
||
}
|
||
|
||
.code-block {
|
||
display: inline-block;
|
||
cursor: text;
|
||
border-radius: 3px;
|
||
|
||
padding: 4px;
|
||
padding-right: 12px;
|
||
|
||
margin: 0;
|
||
|
||
position: relative;
|
||
}
|
||
.code-block-copier {
|
||
cursor: pointer;
|
||
padding: 2px;
|
||
|
||
position: absolute;
|
||
top: 0;
|
||
|
||
border: thin solid;
|
||
border-radius: 3.7px 0px 3.7px 3.7px;
|
||
box-shadow: -2px 2px 3px #0000004a;
|
||
background: linear-gradient(0deg, #f0f0f0, white);
|
||
}
|
||
.inline-code, .code-block {
|
||
background: #F0F0F0;
|
||
}
|
||
blockquote .inline-code,
|
||
blockquote .code-block {
|
||
background: #DEDEDE;
|
||
}
|
||
|
||
.code-keyword {
|
||
font-weight: bold;
|
||
}
|
||
.code-build-in {
|
||
color: blue;
|
||
}
|
||
.code-red {
|
||
color: red;
|
||
font-weight: bold;
|
||
}
|
||
.code-glowing-bracket {
|
||
color: red;
|
||
background-color: #C0C0C0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page-base" id="page-select">
|
||
<div class="page-container" id="page-select-container"></div>
|
||
</div>
|
||
<div class="page-base" id="page-display">
|
||
<div class="page-container" id="page-display-container">
|
||
<div id="page-display-container-body"></div>
|
||
</div>
|
||
</div>
|
||
<div class="page-base" id="splitter"></div>
|
||
<script>
|
||
/* ============================== *\
|
||
misc
|
||
\* ============================== */
|
||
|
||
const selected_name_color = "#eaeaea";
|
||
const pointed_name_color = "#d1faff";
|
||
|
||
var localStorageKey = "";
|
||
|
||
const get_style = (el)=>el.currentStyle || window.getComputedStyle(el);
|
||
|
||
var spl_resize_event = [];
|
||
|
||
/* ============================== *\
|
||
page load
|
||
\* ============================== */
|
||
|
||
var root_folder = null;
|
||
var currently_loading_folder = null;
|
||
|
||
var prev_opened_folders = null;
|
||
var opened_folders = [];
|
||
|
||
var page_by_path = {};
|
||
const define_page_with_path = (path, page)=>{
|
||
|
||
if (page_by_path[path])
|
||
console.error(`page with path "${path}" defined multiple times`); else
|
||
page_by_path[path] = page;
|
||
|
||
if (broken_links[path])
|
||
{
|
||
for (let lnk of broken_links[path])
|
||
lnk.fix(page);
|
||
delete broken_links[path];
|
||
}
|
||
|
||
}
|
||
|
||
const on_start_folder = (folder_name, root_page)=>{
|
||
let res = {
|
||
folders: [],
|
||
pages: [],
|
||
content_ref: root_page,
|
||
container: document.createElement("div"),
|
||
name: folder_name,
|
||
};
|
||
|
||
if (root_page)
|
||
{
|
||
root_page.tree_obj = res;
|
||
document.getElementById("page-display-container-body").append(root_page);
|
||
}
|
||
|
||
if (!root_folder)
|
||
{
|
||
root_folder = res;
|
||
res.path = "";
|
||
|
||
localStorageKey = `spec : ${folder_name} : `;
|
||
let prev_opened_folders_str = localStorage[localStorageKey+"opened_folders"];
|
||
prev_opened_folders = prev_opened_folders_str ? JSON.parse(prev_opened_folders_str) : [];
|
||
|
||
res.container.className = "ps-root-container";
|
||
document.getElementById("page-select-container").append(res.container);
|
||
if (root_page) select_page(root_page);
|
||
} else
|
||
{
|
||
currently_loading_folder.folders.push(res);
|
||
res.root = currently_loading_folder;
|
||
res.path = res.root.path + folder_name + '/';
|
||
|
||
res.body = document.createElement("div");
|
||
res.body.className = "ps-folder";
|
||
{
|
||
let ind = prev_opened_folders.indexOf(res.path);
|
||
res.state = ind!==-1;
|
||
if (res.state)
|
||
{
|
||
prev_opened_folders.splice(ind,1);
|
||
opened_folders.push(res.path);
|
||
}
|
||
}
|
||
currently_loading_folder.container.append(res.body);
|
||
|
||
res.update = ()=>{
|
||
res.state_span.innerHTML = String.fromCharCode( res.empty? 0x2022 : res.state ? 0x25BC : 0x25BA );
|
||
res.container.hidden = !res.state;
|
||
}
|
||
res.reverse_state = ()=>{
|
||
res.state = !res.state;
|
||
|
||
if (res.state)
|
||
opened_folders.push(res.path); else
|
||
opened_folders.splice(opened_folders.indexOf(res.path), 1);
|
||
localStorage[localStorageKey+"opened_folders"] = JSON.stringify(opened_folders);
|
||
|
||
res.update();
|
||
};
|
||
|
||
res.state_span = document.createElement("span");
|
||
res.state_span.className = "dot-page-root";
|
||
res.state_span.innerHTML = String.fromCharCode( 0x2022 );
|
||
res.body.append(res.state_span);
|
||
|
||
res.name_span = document.createElement("span");
|
||
res.name_span.className = "ps-folder-name";
|
||
res.name_span.innerHTML = folder_name;
|
||
if (root_page) root_page.name_span = res.name_span;
|
||
res.body.append(res.name_span);
|
||
//
|
||
if (root_page) res.name_span.addEventListener("click", ()=>select_page(root_page));
|
||
res.name_span.addEventListener("mouseenter", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor=pointed_name_color; });
|
||
res.name_span.addEventListener("mouseleave", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor="inherit"; });
|
||
|
||
res.container.className = "ps-container";
|
||
res.body.append(res.container);
|
||
|
||
}
|
||
|
||
currently_loading_folder = res;
|
||
if (root_page) fix_links(root_page);
|
||
}
|
||
|
||
const on_page_added = (page)=>{
|
||
document.getElementById("page-display-container-body").append(page);
|
||
|
||
let res = {
|
||
root: currently_loading_folder,
|
||
name: page.getAttribute("page_name"),
|
||
};
|
||
res.path = currently_loading_folder.path + res.name;
|
||
|
||
res.cont_div = document.createElement("div");
|
||
res.cont_div.className = "ps-page";
|
||
currently_loading_folder.container.append(res.cont_div);
|
||
|
||
res.dot_span = document.createElement("span");
|
||
res.dot_span.className = "dot-page-root";
|
||
res.dot_span.innerHTML = String.fromCharCode( 0x2022 );
|
||
res.cont_div.append(res.dot_span);
|
||
|
||
res.name_span = document.createElement("span");
|
||
res.name_span.innerHTML = res.name + "<br>";
|
||
res.name_span.className = "ps-page-name";
|
||
res.name_span.addEventListener("click", ()=>select_page(page));
|
||
res.name_span.addEventListener("mouseenter", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor=pointed_name_color; });
|
||
res.name_span.addEventListener("mouseleave", ()=>{ if (is_selected(res.name_span)) res.name_span.style.backgroundColor="inherit"; });
|
||
res.cont_div.append(res.name_span);
|
||
page.name_span = res.name_span;
|
||
|
||
res.content_ref = page;
|
||
page.tree_obj = res;
|
||
|
||
currently_loading_folder.pages.push(res);
|
||
define_page_with_path(res.path, page);
|
||
fix_links(page);
|
||
|
||
if (!selected_page && !document.location.hash) select_page(page);
|
||
}
|
||
|
||
const on_end_folder = ()=>{
|
||
|
||
if (currently_loading_folder!=root_folder) {
|
||
let folder = currently_loading_folder;
|
||
|
||
if ( folder.folders.length || folder.pages.length )
|
||
{
|
||
folder.state_span.className = "arrow-page-root";
|
||
folder.state_span.addEventListener("click", folder.reverse_state);
|
||
folder.name_span.addEventListener("dblclick", folder.reverse_state);
|
||
currently_loading_folder.update();
|
||
} else
|
||
folder.empty = true;
|
||
|
||
}
|
||
|
||
if (currently_loading_folder !== root_folder)
|
||
{
|
||
|
||
if (!currently_loading_folder.content_ref)
|
||
{
|
||
let res = document.createElement("div");
|
||
res.hidden = true;
|
||
res.name_span = currently_loading_folder.name_span;
|
||
res.name_span.addEventListener("click", ()=>select_page(res));
|
||
document.getElementById("page-display-container-body").append(res);
|
||
currently_loading_folder.content_ref = res;
|
||
res.tree_obj = currently_loading_folder;
|
||
|
||
if (currently_loading_folder.folders.length)
|
||
{
|
||
let h = document.createElement("h1");
|
||
h.innerHTML = "Под-папки:";
|
||
res.append(h);
|
||
|
||
let l = document.createElement("ul");
|
||
for (let folder of currently_loading_folder.folders)
|
||
{
|
||
let li = document.createElement("li");
|
||
li.innerHTML = folder.name_span.innerHTML;
|
||
make_smart_link(li, folder.content_ref);
|
||
l.append(li);
|
||
}
|
||
res.append(l);
|
||
|
||
}
|
||
|
||
if (currently_loading_folder.pages.length)
|
||
{
|
||
let h = document.createElement("h1");
|
||
h.innerHTML = "Страницы:";
|
||
res.append(h);
|
||
|
||
let l = document.createElement("ul");
|
||
for (let page of currently_loading_folder.pages)
|
||
{
|
||
let li = document.createElement("li");
|
||
li.innerHTML = page.name_span.innerHTML;
|
||
make_smart_link(li, page.content_ref);
|
||
l.append(li);
|
||
}
|
||
res.append(l);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
define_page_with_path(currently_loading_folder.path, currently_loading_folder.content_ref);
|
||
currently_loading_folder = currently_loading_folder.root;
|
||
}
|
||
|
||
}
|
||
|
||
/* ============================== *\
|
||
page display
|
||
\* ============================== */
|
||
|
||
var selected_page = null;
|
||
var scroll_by_page = {};
|
||
const is_selected = (name_span)=>(!selected_page || selected_page.name_span!==name_span);
|
||
const select_page = (new_page)=>{
|
||
if (selected_page == new_page) return;
|
||
document.location.hash = new_page.tree_obj.path;
|
||
fix_element(new_page);
|
||
|
||
let container = document.getElementById("page-display-container");
|
||
|
||
if (selected_page)
|
||
{
|
||
scroll_by_page[selected_page.tree_obj.path] = container.scrollTop;
|
||
selected_page.hidden = true;
|
||
let name_span = selected_page.name_span;
|
||
if (name_span) name_span.style.backgroundColor = "inherit";
|
||
}
|
||
|
||
selected_page = new_page;
|
||
if (selected_page)
|
||
{
|
||
selected_page.hidden = false;
|
||
let name_span = selected_page.name_span;
|
||
if (name_span) name_span.style.backgroundColor = selected_name_color;
|
||
|
||
let tree_obj = new_page.tree_obj.root;
|
||
while (tree_obj)
|
||
{
|
||
if ( tree_obj.state==false && tree_obj.reverse_state ) tree_obj.reverse_state();
|
||
tree_obj = tree_obj.root;
|
||
}
|
||
|
||
container.scrollTop = scroll_by_page[selected_page.tree_obj.path] || 0;
|
||
}
|
||
}
|
||
|
||
const make_smart_link = (lnk, page)=>{
|
||
lnk.className = "smart-link";
|
||
lnk.addEventListener("click", ()=>select_page(page));
|
||
}
|
||
|
||
var broken_links = {};
|
||
const define_broken_lnk = (path, source, fixer)=>{
|
||
if (!broken_links[path]) broken_links[path] = [];
|
||
broken_links[path].push({
|
||
source: source,
|
||
fix: fixer,
|
||
});
|
||
}
|
||
|
||
const fix_links = (page)=>{
|
||
|
||
for (let lnk of page.getElementsByTagName('a'))
|
||
{
|
||
lnk.path = lnk.getAttribute("path");
|
||
if (!lnk.path) continue;
|
||
lnk.removeAttribute("path");
|
||
|
||
start_folder = currently_loading_folder;
|
||
while (lnk.path.startsWith("../"))
|
||
{
|
||
start_folder = start_folder.root;
|
||
lnk.path = lnk.path.substr(3);
|
||
}
|
||
lnk.path = start_folder.path+lnk.path;
|
||
|
||
let lnk_page = page_by_path[lnk.path];
|
||
if (lnk_page)
|
||
make_smart_link(lnk, lnk_page); else
|
||
define_broken_lnk(lnk.path, page.tree_obj.path, new_page=>make_smart_link(lnk, new_page))
|
||
|
||
}
|
||
|
||
}
|
||
|
||
const code_words_color = {
|
||
"pas": {
|
||
"keyword": [
|
||
"begin","end", "var", "uses", "as", "new", "try", "except", "on", "do", "const",
|
||
"procedure", "function", "lock", "type", "class","record", "sizeof","typeof", "external",
|
||
"static", "array", "where", "or","and", "public","private", "property","constructor", "default",
|
||
"of", "if","then","else", "div","mod", "extensionmethod", "while",
|
||
],
|
||
"build-in": [
|
||
"nil", "self", "true", "false",
|
||
"string","char", "object", "pointer", "single","real",
|
||
"shortint", "smallint", "integer", "int64",
|
||
"byte", "word", "longword", "cardinal", "uint64",
|
||
],
|
||
"red": ["ToDo"],
|
||
},
|
||
"cl-c": {
|
||
"keyword": ["__kernel", "__global"],
|
||
"build-in": ["int", "void"],
|
||
},
|
||
"default": {
|
||
|
||
}
|
||
}
|
||
|
||
const fix_element = (page)=>{
|
||
if (page.fixed) return;
|
||
page.fixed = true;
|
||
|
||
for (let spoiler of page.getElementsByClassName('spoiler'))
|
||
{
|
||
let get_spoiler_text = ()=> + ' ' + spoiler.getAttribute('summary');
|
||
|
||
let wrap = document.createElement('p');
|
||
wrap.className = "spoiler-wrap";
|
||
wrap.update = ()=>{
|
||
wrap.state_span.innerHTML = String.fromCharCode( spoiler.hidden ? 0x25BA : 0x25BC );
|
||
wrap.style.borderLeftWidth = spoiler.hidden ? 0 : 1;
|
||
wrap.name_span.style.borderBottomWidth = spoiler.hidden ? 1 : 0;
|
||
wrap.style.marginLeft = spoiler.hidden ? 1 : 0;
|
||
}
|
||
|
||
wrap.state_span = document.createElement("span");
|
||
wrap.append(wrap.state_span);
|
||
|
||
wrap.name_span = document.createElement("span");
|
||
wrap.name_span.className = "spoiler-name";
|
||
wrap.name_span.innerHTML = spoiler.getAttribute("summary");
|
||
wrap.append(wrap.name_span);
|
||
|
||
wrap.update();
|
||
wrap.reverse_state = ()=>{
|
||
spoiler.hidden = !spoiler.hidden;
|
||
wrap.update();
|
||
}
|
||
|
||
wrap.state_span.addEventListener("click", wrap.reverse_state);
|
||
wrap.name_span.addEventListener("click", wrap.reverse_state);
|
||
|
||
wrap.state_span.style.cursor = "pointer";
|
||
wrap.name_span.style.cursor = "pointer";
|
||
|
||
spoiler.replaceWith(wrap);
|
||
wrap.append(spoiler);
|
||
}
|
||
|
||
for (let code of page.getElementsByTagName('code'))
|
||
{
|
||
let w_to_regex = (w)=>`(?<!\\w)${w}(?!\\w)`;
|
||
|
||
let code_html = code.innerHTML;
|
||
|
||
// Автоопределение языка кода
|
||
if (!code.className)
|
||
{
|
||
let best = {lang: null, c: 0};
|
||
let multiple_best = true;
|
||
|
||
for (let lang_name in code_words_color)
|
||
{
|
||
let c = 0;
|
||
for (let wordt in code_words_color[lang_name])
|
||
for (let w of code_words_color[lang_name][wordt])
|
||
{
|
||
var m = code_html.match(new RegExp( w_to_regex(w), "gi" ));
|
||
if (m) c += m.length;
|
||
}
|
||
|
||
if (best.c == c)
|
||
multiple_best = true; else
|
||
if (best.c < c)
|
||
{
|
||
multiple_best = false;
|
||
best.lang = lang_name;
|
||
best.c = c;
|
||
}
|
||
}
|
||
|
||
if (multiple_best)
|
||
code.className = "language-default"; else
|
||
code.className = "language-" + best.lang;
|
||
}
|
||
|
||
// Подсветка особых слов в коде
|
||
{
|
||
let lang = code.className.substr("language-".length);
|
||
let curr_cw = code_words_color[lang];
|
||
if (!curr_cw) curr_cw = code_words_color["default"];
|
||
for (let wordt in curr_cw)
|
||
code_html = code_html.replace(
|
||
new RegExp(curr_cw[wordt].map(w_to_regex).join('|'),"gi"),
|
||
w=> `<span class="code-${wordt}">${w}</span>`
|
||
);
|
||
}
|
||
|
||
// Выделение скобок
|
||
{
|
||
var br_types = {
|
||
op: ["(", "[", "{", "<", "'"],
|
||
cl: [")", "]", "}", ">", "'"],
|
||
}
|
||
for (let op in br_types)
|
||
for (let i=0; i<br_types[op].length; i++)
|
||
code_html = code_html.replace(
|
||
new RegExp('\\'+br_types[op][i], "g"),
|
||
`<span class=bracket ${ op=="op" ? "op=true" : "" } bt=${i}>${br_types[op][i]}</span>`
|
||
);
|
||
|
||
}
|
||
|
||
// Выделение первой строки
|
||
if (code.parentElement.tagName == "PRE")
|
||
{
|
||
let ind = code_html.indexOf('\n');
|
||
if (ind != code_html.length-1)
|
||
code_html = `<span class="code-first-line">${code_html.slice(0,ind)}</span>${code_html.slice(ind)}`;
|
||
}
|
||
|
||
code.innerHTML = code_html;
|
||
code.firstLine = code.children[0];
|
||
if (code.firstLine && code.firstLine.className != "code-first-line") delete code.firstLine;
|
||
|
||
// Подсветка скобок
|
||
{
|
||
let br_st = [];
|
||
for (let obj2 of code.getElementsByClassName("bracket"))
|
||
{
|
||
let b2t = obj2.getAttribute("bt");
|
||
|
||
if (obj2.getAttribute("op"))
|
||
br_st.push({
|
||
obj: obj2,
|
||
bt: b2t,
|
||
}); else
|
||
{
|
||
let b1 = br_st.pop();
|
||
let b0 = null;
|
||
if (!b1) continue;
|
||
if (b1.obj == obj2.parentElement)
|
||
{
|
||
b0 = b1;
|
||
b1 = br_st.pop();
|
||
}
|
||
if (!b1) continue;
|
||
if (b1.bt == b2t)
|
||
{
|
||
let obj1 = b1.obj;
|
||
|
||
let on_enter = ()=>{
|
||
obj1.className = "code-glowing-bracket";
|
||
obj2.className = "code-glowing-bracket";
|
||
}
|
||
let on_leave = ()=>{
|
||
obj1.className = null;
|
||
obj2.className = null;
|
||
}
|
||
|
||
obj1.addEventListener("mouseenter", on_enter);
|
||
obj2.addEventListener("mouseenter", on_enter);
|
||
obj1.addEventListener("mouseleave", on_leave);
|
||
obj2.addEventListener("mouseleave", on_leave);
|
||
} else
|
||
{
|
||
br_st.push(b1);
|
||
if (b0) br_st.push(b0);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
if (code.parentElement.tagName == "PRE")
|
||
{
|
||
let pre = code.parentElement;
|
||
pre.className = 'code-block';
|
||
|
||
let wrap = document.createElement('p');
|
||
pre.replaceWith(wrap);
|
||
wrap.append(pre);
|
||
|
||
} else
|
||
code.className = "inline-code";
|
||
|
||
}
|
||
|
||
page.style.visibility = "hidden";
|
||
page.hidden = false;
|
||
|
||
for (let code of page.getElementsByTagName('code'))
|
||
{
|
||
if (code.firstLine)
|
||
{
|
||
let pre = code.parentElement;
|
||
|
||
let copy_button = document.createElement('div');
|
||
copy_button.className = "code-block-copier";
|
||
copy_button.innerText = "Копировать";
|
||
copy_button.style.visibility = "hidden";
|
||
pre.append(copy_button);
|
||
|
||
copy_button.addEventListener("click", ()=>{
|
||
|
||
if (document.selection) { // IE
|
||
var range = document.body.createTextRange();
|
||
range.moveToElementText(code);
|
||
range.select();
|
||
} else if (window.getSelection) { // other browsers
|
||
var range = document.createRange();
|
||
range.selectNode(code);
|
||
window.getSelection().removeAllRanges();
|
||
window.getSelection().addRange(range);
|
||
}
|
||
|
||
var text = code.innerText;
|
||
navigator.clipboard.writeText(text.substring(0,text.length-1));
|
||
});
|
||
|
||
copy_button.reset_right = ()=>{
|
||
pre_st = get_style(pre);
|
||
let container = document.getElementById("page-display-container");
|
||
let x = pre.getBoundingClientRect().right - (container.getBoundingClientRect().left+container.clientWidth);
|
||
if (x<0) x = 0; else
|
||
{
|
||
let max_x = pre.offsetWidth - copy_button.offsetWidth;
|
||
if (x>max_x) x = max_x;
|
||
}
|
||
copy_button.style.right = x;
|
||
};
|
||
let try_reset_right = ()=>{
|
||
if (!copy_button.hidden)
|
||
copy_button.reset_right();
|
||
};
|
||
spl_resize_event.push(try_reset_right);
|
||
window.addEventListener("resize", try_reset_right);
|
||
|
||
pre.addEventListener("mouseenter", ()=>{
|
||
copy_button.hidden = false;
|
||
copy_button.reset_right();
|
||
});
|
||
pre.addEventListener("mouseleave", ()=>copy_button.hidden = true);
|
||
|
||
copy_button.addEventListener("mousedown", ()=>copy_button.style.background = "linear-gradient(180deg, rgb(240, 240, 240), white)");
|
||
copy_button.addEventListener("mouseup", ()=>copy_button.style.background = "linear-gradient( 0deg, rgb(240, 240, 240), white)");
|
||
copy_button.addEventListener("mouseleave", ()=>copy_button.style.background = "linear-gradient( 0deg, rgb(240, 240, 240), white)");
|
||
|
||
pre.style.minWidth =
|
||
code.firstLine.offsetWidth +
|
||
copy_button.offsetWidth +
|
||
0
|
||
;
|
||
|
||
copy_button.hidden = true;
|
||
copy_button.style.visibility = null;
|
||
}
|
||
|
||
}
|
||
|
||
page.hidden = true;
|
||
page.style.visibility = null;
|
||
}
|
||
|
||
/* ============================== *\
|
||
init
|
||
\* ============================== */
|
||
|
||
{
|
||
if (document.location.hash)
|
||
{
|
||
let get_hash = ()=>decodeURIComponent(document.location.hash.substr(1));
|
||
let define_hash_lnk = ()=>define_broken_lnk(
|
||
get_hash(),
|
||
"document.location.hash",
|
||
page=>select_page(page)
|
||
);
|
||
define_hash_lnk();
|
||
window.addEventListener("hashchange", ()=>{
|
||
let hash_page = page_by_path[get_hash()];
|
||
if (hash_page)
|
||
select_page(hash_page); else
|
||
define_hash_lnk();
|
||
});
|
||
}
|
||
|
||
let page_select = document.getElementById("page-select");
|
||
let page_display = document.getElementById("page-display");
|
||
let splitter = document.getElementById("splitter");
|
||
|
||
let ww = window.innerWidth;
|
||
let wh = window.innerHeight;
|
||
|
||
for (let cont of document.getElementsByClassName("page-container"))
|
||
{
|
||
let par = cont.parentElement;
|
||
par.update_cont = (w)=>{
|
||
cont.style.width = w-15;
|
||
cont.style.height = wh-15;
|
||
};
|
||
}
|
||
|
||
let spl_X = ww * 0.30;
|
||
const reset_spl = ()=>{
|
||
ww = window.innerWidth;
|
||
wh = window.innerHeight;
|
||
|
||
page_select.style.height = wh + "px";
|
||
splitter.style.height = wh + "px";
|
||
page_display.style.height = wh + "px";
|
||
|
||
if (ww-5<spl_X) spl_X = ww-5;
|
||
if (spl_X<5) spl_X = 5;
|
||
|
||
page_select.style.width = spl_X + "px";
|
||
splitter.style.left = spl_X + "px";
|
||
let x2 = spl_X+splitter.clientWidth;
|
||
let w2 = ww - x2;
|
||
page_display.style.left = x2 + "px";
|
||
page_display.style.width = w2 + "px";
|
||
|
||
page_select.update_cont(spl_X);
|
||
page_display.update_cont(w2);
|
||
|
||
for (let handler of spl_resize_event)
|
||
handler();
|
||
}
|
||
reset_spl();
|
||
|
||
window.addEventListener("resize", ()=>reset_spl());
|
||
|
||
splitter.addEventListener("dblclick", ()=>{
|
||
let w = 0;
|
||
let psc = document.getElementsByClassName("page-container")[0];
|
||
let psc2 = psc.children[0];
|
||
for (let n of psc2.children)
|
||
if (n.clientWidth>w) w = n.clientWidth;
|
||
|
||
let get_margin = (el)=>{
|
||
let style = get_style(el);
|
||
return parseFloat(style.marginLeft) + parseFloat(style.marginRight);
|
||
}
|
||
|
||
spl_X = w +
|
||
get_margin(psc) +
|
||
get_margin(psc2)
|
||
;
|
||
reset_spl();
|
||
});
|
||
|
||
let spl_grabed = false;
|
||
splitter.addEventListener("mousedown", (e)=>{
|
||
spl_grabed=true;
|
||
e.preventDefault();
|
||
});
|
||
window.addEventListener("mousemove", (e)=>{if (spl_grabed) {
|
||
spl_X = e.clientX - splitter.clientWidth/2;
|
||
reset_spl();
|
||
e.preventDefault();
|
||
}});
|
||
window.addEventListener("mouseup", ()=>spl_grabed=false);
|
||
|
||
}
|
||
|
||
window.onload = ()=>{
|
||
|
||
for (path in broken_links)
|
||
console.error(`Page "${path}" referenced ${broken_links[path].length} times but not found:`, broken_links[path].map(lnk=>lnk.source));
|
||
|
||
localStorage[localStorageKey+"opened_folders"] = JSON.stringify(opened_folders);
|
||
if (prev_opened_folders.length) console.log("folders were marked as opened, but not found:", prev_opened_folders);
|
||
delete prev_opened_folders;
|
||
|
||
}
|
||
</script>
|
||
<script>on_start_folder("CL ABC", null)</script>
|
||
<script>on_start_folder("Общие сведения", null)</script>
|
||
<div id="page-1" page_name="О справке" hidden=true>
|
||
<p>Данная справка относится к модулю <code>OpenCLABC</code>, входящему в состав стандартных модулей языка <code>PascalABC.NET</code>.</p>
|
||
<p>Модуль <code>OpenCLABC</code> это высокоуровневая оболочка модуля <code>OpenCL</code>.<br />
|
||
Это значит, что с <code>OpenCLABC</code> можно писать гораздо меньше кода в больших и сложных программах,
|
||
однако такой же уровень микроконтроля как с модулем <code>OpenCL</code> недоступен.
|
||
Например, напрямую управлять <code>cl_event</code>'ами в <code>OpenCLABC</code> невозможно.
|
||
Вместо этого надо использовать операции с очередями (например, сложение и умножение очередей)</p>
|
||
<p>Справка модуля <code>OpenCL</code> отсутствует. Вместо неё смотрите:</p>
|
||
<ul>
|
||
<li><a href="%D0%93%D0%B0%D0%B9%D0%B4%20%D0%BF%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E%20OpenGL%20%D0%B8%20OpenCL.html">Общий гайд по использованию модулей <code>OpenGL</code> и <code>OpenCL</code></a></li>
|
||
<li><a href="https://www.khronos.org/registry/OpenCL/">Контейнер справок библиотеки <code>OpenCL</code>, на которой основан модуль <code>OpenCL</code></a></li>
|
||
</ul>
|
||
<p>Если в справке или модуле найдена ошибка, или чего-либо не хватает - пишите в <a href="https://github.com/SunSerega/POCGL/issues">issue</a>.</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-1"))</script>
|
||
<div id="page-2" page_name="Термины, которые часто путают новички" hidden=true>
|
||
<ul>
|
||
<li><p>CPU — Центральное Процессорное Устройство (процессор);</p>
|
||
</li>
|
||
<li><p>GPU — Графическое Процессорное Устройство (видеокарта);</p>
|
||
</li>
|
||
<li><p>Команда — запрос на выполнение чего-либо. К примеру:</p>
|
||
<ul>
|
||
<li>Запрос на запуск программы на GPU;</li>
|
||
<li>Запрос на начало чтения данных из памяти GPU в оперативную память;</li>
|
||
</ul>
|
||
<p><strong>Называть процедуры и функции командами ошибочно!</strong></p>
|
||
</li>
|
||
<li><p>Подпрограмма — процедура или функция;</p>
|
||
</li>
|
||
<li><p>Метод — особая подпрограмма, вызываемая через экземпляр:</p>
|
||
<ul>
|
||
<li>К примеру, метод <code>Context.SyncInvoke</code> выглядит в коде как <code>cont.SyncInvoke(...)</code>, где <code>cont</code> — переменная типа <code>Context</code>;</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<p>Остальные непонятные термины можно найти в справке <code>PascalABC.NET</code> или в интернете.</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-2"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-3" page_name="" hidden=true>
|
||
<p>Сам модуль <code>OpenCLABC</code> - оболочка модуля <code>OpenCL</code>. Это значит, что внутри он использует
|
||
содержимое <code>OpenCL</code>, но при подключении - показывает только свой личный функционал.</p>
|
||
<p>Так же множество типов из <code>OpenCLABC</code> являются оболочками типов из <code>OpenCL</code>.</p>
|
||
<p>Тип <code>CommandQueue</code> использует тип <code>cl_command_queue</code>, но предоставляет очень много не связанного с <code>cl_command_queue</code> функционала.</p>
|
||
<p>"Простые" типы-оболочки модуля <code>OpenCLABC</code> предоставляют только функционал соответствующего
|
||
типа из модуля <code>OpenCL</code>... в более презентабельном виде. Из общего - у таких типов есть:</p>
|
||
<ul>
|
||
<li><p>Свойство <code>.Native</code>, возвращающее внутренний объект из модуля <code>OpenCL</code>.<br />
|
||
Если вам не пришлось, по какой-либо причине, использовать <code>OpenCLABC</code> и <code>OpenCL</code> вместе - это свойство может понадобится только для дебага.</p>
|
||
</li>
|
||
<li><p>Свойство <code>.Properties</code>, возвращающее объект свойств внутреннего объекта.<br />
|
||
Свойства неуправляемого объекта никак не обрабатываются и не имеют описаний.
|
||
Но типы этих свойств всё равно преобразуются в управляемые (особо заметно на строках и массивах).</p>
|
||
</li>
|
||
</ul>
|
||
<hr />
|
||
<h1>Список простых типов-оболочек:</h1>
|
||
<ul>
|
||
<li><a path="Platform/"> Platform </a> - Платформа, объединяющая несколько совместимых устройств;</li>
|
||
<li><a path="Device/"> Device </a> - Устройство, поддерживающее OpenCL;</li>
|
||
<li><a path="Context/"> Context </a> - Контекст выполнения;</li>
|
||
<li><a path="Buffer/"> Buffer </a> - Область памяти GPU;</li>
|
||
<li><a path="Kernel/"> Kernel </a> - Подпрограмма, выполняемая на GPU;</li>
|
||
<li><a path="ProgramCode/"> ProgramCode </a> - Контейнер kernel'ов.</li>
|
||
</ul>
|
||
</div>
|
||
<script>on_start_folder("Простые оболочки", document.getElementById("page-3"))</script>
|
||
<div id="page-4" page_name="" hidden=true>
|
||
<p>В списке устройств контекста могут быть только совместимые друг с другом устройства.<br />
|
||
Коллекция совместимых устройств называется платформой и хранится в объектах типа <code>Platform</code>.</p>
|
||
<hr />
|
||
<p>Обычно платформы получают из статического свойства <code>Platform.All</code>:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var pls := Platform.All;
|
||
pls.PrintLines;
|
||
end.
|
||
</code></pre>
|
||
</div>
|
||
<script>on_start_folder("Platform", document.getElementById("page-4"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-5" page_name="" hidden=true>
|
||
<p>Обычно устройства получают статическим методом <code>Device.GetAllFor</code>:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
foreach var pl in Platform.All do
|
||
begin
|
||
Writeln(pl);
|
||
var dvcs := Device.GetAllFor(pl, DeviceType.DEVICE_TYPE_ALL);
|
||
if dvcs<>nil then dvcs.PrintLines;
|
||
Writeln('='*30);
|
||
end;
|
||
end.
|
||
</code></pre>
|
||
<p>И в большинстве случаев - это всё что вам понадобится.</p>
|
||
<hr />
|
||
<p>Но если где то нужен более тонкий контроль - можно создать несколько виртуальных
|
||
под-устройств, каждому из которых даётся часть ядер изначального устройства.<br />
|
||
Для этого используются методы <code>.Split*</code>:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var dvc := Context.Default.MainDevice;
|
||
|
||
Writeln('Поддерживаемые типы .Spilt-ов:');
|
||
// Пустой список, или DEVICE_PARTITION_BY_COUNTS_LIST_END, если ни 1 не поддерживается
|
||
dvc.Properties.PartitionProperties.PrintLines;
|
||
Writeln('='*30);
|
||
|
||
Writeln('Виртуальные устройства, по 1 ядру каждое:');
|
||
// Если упадёт потому что слишком много
|
||
// устройств - пожалуйста, напишите в issue
|
||
dvc.SplitEqually(1).PrintLines;
|
||
Writeln('='*30);
|
||
|
||
Writeln('Два устройства, 1 и 2 ядра соответственно:');
|
||
dvc.SplitByCounts(1,2).PrintLines;
|
||
|
||
end.
|
||
</code></pre>
|
||
</div>
|
||
<script>on_start_folder("Device", document.getElementById("page-5"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-6" page_name="" hidden=true>
|
||
<p>Для отправки команд в GPU необходим контекст (объект типа <code>Context</code>):<br />
|
||
Он содержит информацию о том, какие устройства будут использоваться для выполнения кода на GPU и хранения содержимого буферов.</p>
|
||
<hr />
|
||
<p>Создать контекст можно конструктором (<code>new Context(...)</code>).<br />
|
||
Контекст можно и не создавать, используя везде свойство <code>Context.Default</code>.<br />
|
||
Неявные очереди всегда используют <code>Context.Default</code>.</p>
|
||
<p>Изначально этому свойству присваивается контекст, использующий GPU, если оно имеется,
|
||
или любое другое устройство, поддерживающее OpenCL, если GPU отсутствует.</p>
|
||
<p>Если устройств поддерживающих <code>OpenCL</code> нет - <code>Context.Default</code> будет <code>nil</code>.<br />
|
||
Однако такая ситуация на практике невозможна, потому что OpenCL поддерживается
|
||
практически всеми современными устройствами, занимающимися выводом изображения на экран.<br />
|
||
Если <code>Context.Default = nil</code> - переустановите графические драйверы.</p>
|
||
<hr />
|
||
<p><code>Context.Default</code> можно перезаписывать.<br />
|
||
Используйте эту возможность только если во всей программе нужен общий контекст, но не стандартный.</p>
|
||
<p>Если ваша программа достаточно сложная чтобы нуждаться в нескольких контекстах - лучше не использовать
|
||
<code>Context.Default</code>. И присвоить ему <code>nil</code>, чтобы не использовать случайно (к примеру, неявной очередью).</p>
|
||
</div>
|
||
<script>on_start_folder("Context", document.getElementById("page-6"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-7" page_name="" hidden=true>
|
||
<p>Программам, выпоняемые на GPU, не удобно использовать оперативную память.
|
||
Поэтому обычно надо выделять память на самом GPU.<br />
|
||
Объекты типа <code>Buffer</code> представляют такую область памяти.</p>
|
||
<hr />
|
||
<p>Буфер создаётся конструктором (<code>new Buffer(...)</code>).</p>
|
||
<p>Но если не передавать контекст в конструктор - память на GPU выделится только при вызове метода <code>Buffer.Init</code>.<br />
|
||
Если вызвать <code>Buffer.Init</code> 2 раза - память освободится и выделится заново.</p>
|
||
<p>Когда на GPU выделяется память - она <strong>НЕ</strong> очищается нулями, а значит содержит мусорные данные.</p>
|
||
<p>Если метод <code>Buffer.Init</code> не был вызван до первой операции чтения/записи - он будет вызван автоматически.
|
||
В таком случае в качестве контекста в котором выделяется память берётся тот,
|
||
на котором вызвали <code>.BeginInvoke</code>, который запустил команду чтения/записи буфера.</p>
|
||
<p>Память на GPU можно моментально освободить, вызвав метод <code>Buffer.Dispose</code>.
|
||
Но если снова использовать буфер, у которого освободили память - память выделится заново.</p>
|
||
<p>Если сборщик мусора удаляет объект типа <code>Buffer</code> - <code>.Dispose</code> вызывается автоматически.</p>
|
||
<hr />
|
||
<p>Но кроме выделения памяти GPU - OpenCL так же позволяет выделять память внутри другого буфера.<br />
|
||
Для этого используется тип <code>SubBuffer</code>:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var c := Context.Default;
|
||
|
||
// Не обязательно MainDevice, можно взять любое устройство из контекста
|
||
var align := c.MainDevice.Properties.MemBaseAddrAlign;
|
||
|
||
var b := new Buffer(align*2, c);
|
||
// size может быть любым, но origin
|
||
// должно быть align*N, где N - целое
|
||
var b1 := new SubBuffer(b, 0, Min(123,align));
|
||
var b2 := new SubBuffer(b, align, align);
|
||
|
||
Writeln(b1);
|
||
Writeln(b2);
|
||
end.
|
||
</code></pre>
|
||
</div>
|
||
<script>on_start_folder("Buffer", document.getElementById("page-7"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-8" page_name="" hidden=true>
|
||
<p>Объект типа <code>Kernel</code> представляет одну подпрограмму в OpenCL-C коде,
|
||
объявленную с ключевым словом <code>__kernel</code>.</p>
|
||
<hr />
|
||
<p>Обычно <code>Kernel</code> создаётся через индексное свойтсво <code>ProgramCode</code>:</p>
|
||
<pre><code>var code: ProgramCode;
|
||
...
|
||
var k := code['KernelName'];
|
||
</code></pre>
|
||
<p>Тут <code>'KernelName'</code> — имя подпрограммы-kernel'а в исходном коде (регистр важен!).</p>
|
||
<hr />
|
||
<p>Так же можно получить список всех kernel'ов объекта <code>ProgramCode</code>, методом <code>ProgramCode.GetAllKernels</code>:</p>
|
||
<pre><code>var code: ProgramCode;
|
||
...
|
||
var ks := code.GetAllKernels;
|
||
ks.PrintLines;
|
||
</code></pre>
|
||
<hr />
|
||
<p>Методы, запускающие <code>Kernel</code> принимают специальные аргументы типа <code>KernelArg</code>, которые передаются в OpenCL-C код.<br />
|
||
Экземпляр <code>KernelArg</code> может быть создан из нескольких типов значений, а точнее:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var k: Kernel;
|
||
var val1 := 3;
|
||
var val2 := 5;
|
||
|
||
k.Exec1(1,
|
||
// Передавать можно:
|
||
|
||
// Буфер
|
||
new Buffer(1),
|
||
// Очередь возвращающую буфер
|
||
HFQ(()->new Buffer(1)),
|
||
// В том числе BufferCommandQueue
|
||
Buffer.Create(1).NewQueue,
|
||
|
||
// Размерное значение
|
||
val1,
|
||
HFQ(()->val1),
|
||
|
||
// И указатель на размерное значение
|
||
// (в kernel попадёт само значение, не указатель)
|
||
@val2,
|
||
// Так нельзя, потому что val1 уже захвачена лямбдой из HFQ
|
||
// @val1,
|
||
// Расширенный набор параметров
|
||
KernelArg.FromPtr(new System.IntPtr(@val2), new System.UIntPtr(sizeof(integer)))
|
||
|
||
);
|
||
end.
|
||
</code></pre>
|
||
<p>Обратите внимание, <code>KernelArg</code> из указателя на <code>val2</code> будет немного эффективнее
|
||
чем <code>KernelArg</code> из самого значения <code>val2</code>. Но эту возможность стоит использовать
|
||
только как тонкую оптимизацию, потому что много чего может пойти не так.
|
||
Если передавать <code>@val2</code> в качестве <code>KernelArg</code> - надо знать все тонкости.</p>
|
||
<hr />
|
||
<p>Если <code>@val2</code> передали вкачестве <code>KernelArg</code>:</p>
|
||
<ol>
|
||
<li><p><code>val2</code> не может быть глобальной переменной или полем класса:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
type
|
||
t1 = class
|
||
val1 := 1; // Не подходит, потому что поле класса
|
||
static val2 := 2; // И статичных полей это тоже касается
|
||
end;
|
||
|
||
var
|
||
val3 := 3; // Глобальные переменные - тоже статические поля
|
||
k: Kernel; // k не важно где объявлена
|
||
|
||
procedure p1;
|
||
// Теоретически подходит, но вообще это плохой стиль кода
|
||
var val4 := 4;
|
||
begin
|
||
|
||
// А val5 однозначно подходит, потому что объявлена не только в той же
|
||
// подпрограмме, но и прямо перед использованием в k.Exec*
|
||
var val5 := 5;
|
||
|
||
k.Exec1(1,
|
||
|
||
// Это единственные 2 переменные, которые можно передавать адресом
|
||
@val4,
|
||
@val5
|
||
|
||
);
|
||
|
||
end;
|
||
|
||
begin end.
|
||
</code></pre>
|
||
</li>
|
||
<li><p><code>val2</code> не должно быть захвачего лямбдой.<br />
|
||
Хотя указатель на <code>val2</code> уже можно захватывать:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var k: Kernel;
|
||
var val1 := 3;
|
||
var val2 := 5;
|
||
|
||
// На val1 всё ещё накладываются все ограничени,
|
||
// когда val1_ptr использована в качестве KernelArg
|
||
// Но к самой val1_ptr эти ограничения не применяются
|
||
var val1_ptr := @val1;
|
||
|
||
k.Exec1(1,
|
||
|
||
val1,
|
||
HFQ(()->val1_ptr^), // захватили val1_ptr, а не val1
|
||
|
||
// val1 нигде не захвачена, поэтому теперь так можно
|
||
@val1,
|
||
val1_ptr // то же самое
|
||
|
||
);
|
||
|
||
end.
|
||
</code></pre>
|
||
</li>
|
||
<li><p>Выходить из подпрограммы, где объявили <code>val2</code> нельзя, пока <code>.Exec</code> не закончит выполнятся.
|
||
Это так же значит, что возвращать очередь, содержащую <code>KernelArg</code> из <code>@val2</code> обычно нельзя.<br />
|
||
Но это можно обойти, если объявлять переменную в другой подпрограмме, а дальше передавать только её адрес:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
var k: Kernel; // Вообще лучше передавать параметром в p2
|
||
|
||
// Обратите внимание - val принимает var-параметром
|
||
// То есть, p2 принимает адрес val
|
||
// (можно так же принимать указатель, но это не так удобно)
|
||
// Иначе переменная будет копироваться при передаче
|
||
// То есть без var перед параметром - val тут
|
||
// будет новой переменной, объявленной в p2 а не в p1
|
||
function p2(var val: integer): CommandQueueBase;
|
||
begin
|
||
|
||
Result := k.NewQueue.AddExec1(1,
|
||
|
||
@val
|
||
|
||
);
|
||
|
||
end;
|
||
|
||
procedure p1;
|
||
begin
|
||
var val: integer;
|
||
|
||
var q := p2(val);
|
||
// Опять же, q не должна продолжать выпоняться
|
||
// после выхода из p1, потому что тут объявлена val
|
||
Context.Default.SyncInvoke(q);
|
||
|
||
end;
|
||
|
||
begin
|
||
p1;
|
||
end.
|
||
</code></pre>
|
||
</li>
|
||
</ol>
|
||
<p>Компилятор не заставит вас следовать этим ограничениям. И программа может даже работать, игнорируя большинство сказанного тут.<br />
|
||
Но потом вы добавите что то в совсем другой части программы, или запустите её на
|
||
другом компьютере и она вдруг начнёт выводить мусорные значения, или падать с ошибками
|
||
вроде <code>AccessViolationException</code>, которые совершенно не объясняют в чём проблема.</p>
|
||
<p>Если очень интересно откуда берутся эти ограничения - начните с применения .Net декомпилятора к .exe файлам,
|
||
получаемым при компиляции программ с данной страницы. Что останется непонятно - можете спрашивать в issue.</p>
|
||
</div>
|
||
<script>on_start_folder("Kernel", document.getElementById("page-8"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-9" page_name="" hidden=true>
|
||
<p>Обычные программы невозможно запустить на GPU. Для этого надо писать особые программы.<br />
|
||
В контексте OpenCL - эти программы обычно пишутся на языке "OpenCL C" (основанном на языке "C").</p>
|
||
<p>Язык OpenCL-C это часть библиотеки OpenCL, поэтому его справку можно найти <a href="https://www.khronos.org/registry/OpenCL/">там же</a>, где и справку OpenCL.</p>
|
||
<p>В <code>OpenCLABC</code> код на языке "OpenCL C" хранится в объектах типа <code>ProgramCode</code>.<br />
|
||
Объекты этого типа используются только как контейнеры.
|
||
Один объект ProgramCode может содержать любое количествово подпрограмм-kernel'ов.</p>
|
||
<hr />
|
||
<h1>Есть 2 способа создать объект типа <code>ProgramCode</code>:</h1>
|
||
<ul>
|
||
<li><p><a path="Создание из исходного кода"> Из исходного кода </a></p>
|
||
</li>
|
||
<li><p><a path="Создание из бинарного файла"> Из прекомпилированного кода </a></p>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<script>on_start_folder("ProgramCode", document.getElementById("page-9"))</script>
|
||
<div id="page-10" page_name="Создание из исходного кода" hidden=true>
|
||
<p>Конструктор <code>ProgramCode</code> (<code>new ProgramCode(...)</code>) принимает тексты исходников программы на языке OpenCL-C.<br />
|
||
<strong>Именно тексты исходников, не имена файлов!</strong></p>
|
||
<p>Так же, как исходники паскаля хранят в .pas файлах, исходники OpenCL-C кода хранят в .cl файлах.
|
||
Но вообще, это не принципиально, потому что код даже не обязательно должен быть в файле.</p>
|
||
<p>Так как конструктор <code>ProgramCode</code> принимает текст - исходники программы на OpenCL-C можно хранить даже в
|
||
строке в .pas программе. Тем не менее, храненить исходники OpenCL-C кода в .cl файлах обычно удобнее всего.</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-10"))</script>
|
||
<div id="page-11" page_name="Создание из бинарного файла" hidden=true>
|
||
<p>После создания объекта типа <code>ProgramCode</code> из исходников можно вызвать
|
||
метод <code>ProgramCode.SerializeTo</code>, чтобы сохранить код в бинарном и прекомпилированном виде.
|
||
Обычно это делается отдельной программой (не той же самой, которая будет использовать этот бинарный код).</p>
|
||
<p>После этого основная программа может создать объект <code>ProgramCode</code>,
|
||
используя статический метод <code>ProgramCode.DeserializeFrom</code>.</p>
|
||
<p>Пример можно найти в папке <code>Прекомпиляция ProgramCode</code> или <a href="https://github.com/SunSerega/POCGL/tree/master/Samples/OpenCLABC/%D0%9F%D1%80%D0%B5%D0%BA%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D1%8F%D1%86%D0%B8%D1%8F%20ProgramCode">тут</a>.</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-11"))</script>
|
||
<script>on_end_folder()</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-12" page_name="" hidden=true>
|
||
<p>Передавать команды для GPU по одной не эффективно.
|
||
Гораздо эффективнее передавать несколько команд сразу.</p>
|
||
<p>Для этого существуют очереди (типы, наследующие от <code>CommandQueue<T></code> и <code>CommandQueueBase</code>).
|
||
Они хранят произвольное количество команд для GPU.
|
||
А при необходимости также и части кода, выполняемые на CPU.</p>
|
||
<hr />
|
||
<h1>Страницы:</h1>
|
||
<ul>
|
||
<li><p><a path="Возвращаемое значение очередей/"> Возвращаемое значение </a></p>
|
||
</li>
|
||
<li><p><a path="Создание очередей/"> Создание </a></p>
|
||
</li>
|
||
<li><p><a path="Выполнение очередей/"> Выполнение </a></p>
|
||
</li>
|
||
<li><p><a path="Вложенные очереди/"> Вложенные очереди </a></p>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<script>on_start_folder("Очередь [команд] (CommandQueue)", document.getElementById("page-12"))</script>
|
||
<div id="page-13" page_name="" hidden=true>
|
||
<p>У каждого типа-очереди есть свой тип возвращаемого значения.<br />
|
||
К примеру, так объявляется переменная в которую можно будет сохранить очередь, возвращающую <code>integer</code>:</p>
|
||
<pre><code>var Q1: CommandQueue<integer>;
|
||
</code></pre>
|
||
<p>Очереди, созданные из буфера или kernel'а возващают свой <code>Buffer</code>/<code>Kernel</code> соответственно, из которого были созданы;<br />
|
||
Очереди, созданные с <code>HFQ</code> - значение, которое вернёт переданная функция;<br />
|
||
Очереди, созданные с <code>HPQ</code> - значение типа <code>object</code> (и всегда <code>nil</code>).</p>
|
||
<p>К примеру:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
/// Вывод типа и значения объекта
|
||
procedure OtpObject(o: object) :=
|
||
Writeln( $'{o?.GetType}[{_ObjectToString(o)}]' );
|
||
// "o?.GetType" это короткая форма "o=nil ? nil : o.GetType",
|
||
// то есть, берём или тип объекта, или nil если сам объект nil
|
||
// _ObjectToString это функция, которую использует Writeln для форматирования значений
|
||
|
||
begin
|
||
var b0 := new Buffer(1);
|
||
|
||
// Тип - буфер, потому что очередь создали из буфера
|
||
OtpObject( Context.Default.SyncInvoke( b0.NewQueue ) );
|
||
|
||
// Тип - Int32 (то есть integer), потому что это тип по умолчанию для выражения (5)
|
||
OtpObject( Context.Default.SyncInvoke( HFQ( ()->5 ) ) );
|
||
|
||
// Тип - string, по той же причине
|
||
OtpObject( Context.Default.SyncInvoke( HFQ( ()->'abc' ) ) );
|
||
|
||
// Тип отсутствует, потому что HPQ возвращает nil
|
||
OtpObject( Context.Default.SyncInvoke( HPQ( ()->Writeln('Выполнилась HPQ') ) ) );
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>После выполнения очереди метод <code>Context.SyncInvoke</code> возвращает то, что вернула очередь.<br />
|
||
А если использовать метод <code>Context.BeginInvoke</code> - возвращаемое значение можно получить с помощью метода <code>CLTask.WaitRes</code>.</p>
|
||
<hr />
|
||
<p>Бывает необходимо хранить несколько очередей, с разными возвращаемыми значениями, вместе. К примеру, в переменной типа <code>List<></code>.<br />
|
||
Но в переменной типа <code>CommandQueue<SomeT></code> можно хранить только очередь с конкретным типом возвращаемого значения <code>SomeT</code>.</p>
|
||
<p>Для того, чтобы хранить очереди с разными возвращаемыми значениями в одной переменной - используется <code>CommandQueueBase</code>.<br />
|
||
<code>CommandQueueBase</code> это особый тип очереди, у которого не указывается возвращаемое значение.<br />
|
||
От него наследует <code>CommandQueue<></code>, поэтому переменной типа <code>CommandQueueBase</code> можно присвоить любую очередь.</p>
|
||
<p>Если попытаться выполнить такую очередь,
|
||
или применять операции преобразования очередей, как <code>.ThenConvert</code>,
|
||
то тип возвращаемого значения будет восприниматься как <code>Object</code>.</p>
|
||
</div>
|
||
<script>on_start_folder("Возвращаемое значение очередей", document.getElementById("page-13"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-14" page_name="" hidden=true>
|
||
<p>Самый простой способ выполнить очередь - вызвать метод <code>Context.SyncInvoke</code>.<br />
|
||
Он синхронно выполняет очередь и вызвращает её результат.</p>
|
||
<p>Но если надо выполнить очередь асинхронно - лучше использовать метод <code>Context.BeginInvoke</code>.<br />
|
||
Он запускает асинхронное выполнение очереди и как только очередь была полностью запущена - возвращает объект типа <code>CLTask<></code>, у которого есть:</p>
|
||
<ul>
|
||
<li>Event'ы (но реализованы методами), позволяющие указать что должно выполнятся когда выполнение <code>CLTask</code> завершится.</li>
|
||
<li>Свойства, возвращающие оригинальную очередь и контекст выполнения.</li>
|
||
<li>Методы для ожидания окончания выполнения и получения результата очереди.</li>
|
||
</ul>
|
||
<p>Метод <code>Context.SyncInvoke</code> реализован как <code>.BeginInvoke(...).WaitRes</code>.
|
||
Поэтому, везде где сказано "... происходит при вызове <code>.BeginInvoke</code>", это же относится и к <code>.SyncInvoke</code>.</p>
|
||
<p>У <code>CLTask</code>, как и у очереди - в <code><></code>, указывается тип возвращаемого значения. То есть:</p>
|
||
<pre><code>var t: CLTask<integer>;
|
||
</code></pre>
|
||
<p>В такую переменную можно сохранить только результат <code>Context.BeginInvoke</code> для очереди типа <code>CommandQueue<integer></code>.</p>
|
||
<p>И как и у <code>CommandQueue</code>:</p>
|
||
<ul>
|
||
<li>Существует так же и тип <code>CLTaskBase</code>, у которого типа возвращаемого значения не указывается.</li>
|
||
<li>Переменной типа <code>CLTaskBase</code> можно присвоить <code>CLTask</code> с любым возвращаемым значением.</li>
|
||
<li><code>Context.BeginInvoke</code> для очереди типа <code>CommandQueueBase</code> возвращает <code>CLTaskBase</code>.</li>
|
||
</ul>
|
||
<p>Если при выполнении возникла ошибка, о ней выведет не полную информацию. Чтобы получить всю информацию - используется <code>try</code>:</p>
|
||
<pre><code>try
|
||
|
||
//ToDo ваш код, вызывающий ошибку
|
||
|
||
except
|
||
// Writeln выводит все внутренние исключения. "e.ToString" тоже.
|
||
on e: Exception do Writeln(e);
|
||
end;
|
||
</code></pre>
|
||
<p>Для этого кода есть стандартный снипет. Чтобы активировать его - напишите <code>tryo</code> и нажмите Shift+Пробел.</p>
|
||
</div>
|
||
<script>on_start_folder("Выполнение очередей", document.getElementById("page-14"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-15" page_name="" hidden=true>
|
||
<h1>Есть всего 10 базовых способов создать очередь:</h1>
|
||
<ol>
|
||
<li><p><a path="Из буфера или kernel'a"> Из буфера / kernel'а </a></p>
|
||
</li>
|
||
<li><p><a path="Из готового результата"> Из готового результата </a></p>
|
||
</li>
|
||
<li><p><a path="С кодом для CPU"> Из обычной подпрограммы </a></p>
|
||
</li>
|
||
<li><p><a path="Комбинируя другие очереди"> Из нескольких других очередей </a></p>
|
||
</li>
|
||
<li><p><a path="Из очереди + преобразователя"> Из очереди + преобразования результата </a></p>
|
||
</li>
|
||
<li><p><a path="Из повторения очереди"> Из повторения одной очереди </a></p>
|
||
</li>
|
||
<li><p><a path="Множественное использование очереди"> Несколько очередей с общей работой </a></p>
|
||
</li>
|
||
<li><p><a path="Особые .Add методы"> Особыми <code>.Add*</code> методами </a></p>
|
||
</li>
|
||
<li><p><a path="Из ожидания очередей"> Из ожидания других очередей </a></p>
|
||
</li>
|
||
<li><p><a path="Не создавая явно"> Не пытаясь </a></p>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
<script>on_start_folder("Создание очередей", document.getElementById("page-15"))</script>
|
||
<div id="page-16" page_name="Из буфера или kernel'a" hidden=true>
|
||
<p>Самый просто способ создать очередь — выбрать объект типа <code>Buffer</code> или <code>Kernel</code> и вызвать для него метод <code>.NewQueue</code>.</p>
|
||
<p>Полученная очередь будет иметь особый тип: <code>BufferCommandQueue</code>/<code>KernelCommandQueue</code> для буфера/kernel'а соответственно.<br />
|
||
К такой очереди можно добавлять команды, вызывая её методы, имена которых начинаются с <code>.Add...</code>.</p>
|
||
<p>К примеру:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
// Буфер достаточного размера чтоб содержать 3 значения типа integer
|
||
var b := new Buffer( 3*sizeof(integer) );
|
||
|
||
// Создаём очередь
|
||
var q := b.NewQueue;
|
||
|
||
// Добавлять команды в полученную очередь можно вызывая соответствующие методы
|
||
q.AddWriteValue(1, 0*sizeof(integer) );
|
||
|
||
// Методы, добавляющие команду в очередь - возвращают очередь, для которой их вызвали (не копию а ссылку на оригинал)
|
||
// Поэтому можно добавлять по несколько команд в 1 строчке:
|
||
q.AddWriteValue(5, 1*sizeof(integer) ).AddWriteValue(7, 2*sizeof(integer) );
|
||
// Все команды в q будут выполняться последовательно
|
||
|
||
Context.Default.SyncInvoke(q);
|
||
|
||
// Вообще чтение тоже надо делать через очереди, но для простого примера - и неявные очереди подходят
|
||
b.GetArray1&<integer>(3).Println;
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Также, очереди <code>BufferCommandQueue</code>/<code>KernelCommandQueue</code> можно создавать из очередей, возвращающих <code>Buffer</code>/<code>Kernel</code> соответственно. Для этого используется конструктор:</p>
|
||
<pre><code>var q0: CommandQueue<Buffer>;
|
||
...
|
||
var q := new BufferCommandQueue(q0);
|
||
</code></pre>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-16"))</script>
|
||
<div id="page-17" page_name="Из готового результата" hidden=true>
|
||
<p>Переменной очереди можно присвоить значение, тип которого совпадает с возвращаемым значением очереди:</p>
|
||
<pre><code>var q: CommandQueue<integer> := 5;
|
||
</code></pre>
|
||
<p>Этот код присваевает переменной <code>q</code> константную очередь, которая ничего не выполняет и возвращает <code>5</code>.</p>
|
||
<p>Получить значение, из которого создали константную очередь, можно преобразовав её к <code>ConstQueue<></code>:</p>
|
||
<pre><code>if q is ConstQueue<integer>(var cq) then
|
||
Writeln($'Очередь была создана из значения ({cq.Val})') else
|
||
Writeln($'Очередь не константная');
|
||
</code></pre>
|
||
<hr />
|
||
<p><code>CommandQueueBase</code> также можно создать из возвращаемого значения, но для этого тип значения должен быть <code>Object</code>:</p>
|
||
<pre><code>var q: CommandQueueBase := 5 as object;
|
||
</code></pre>
|
||
<p>Чтобы получить значение, из которого создали константную очередь, когда не знаете его тип - используйте интерфейс <code>IConstQueue</code>:</p>
|
||
<pre><code>if q is IConstQueue(var cq) then
|
||
Writeln($'Очередь была создана из значения ({cq.GetConstVal})') else
|
||
Writeln($'Очередь не константная');
|
||
</code></pre>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-17"))</script>
|
||
<div id="page-18" page_name="С кодом для CPU" hidden=true>
|
||
<p>Иногда между командами для GPU надо вставить выполнение обычного кода на CPU.
|
||
А разрывать для этого очередь на две части - плохо, потому что
|
||
одна целая очередь всегда выполнится быстрее двух её частей.</p>
|
||
<p>Для таких случае существуют глобальные подпрограммы <code>HFQ</code> и <code>HPQ</code>:</p>
|
||
<p>HFQ — Host Function Queue<br />
|
||
HPQ — Host Procedure Queue<br />
|
||
(Хост в контексте OpenCL - это CPU, потому что с него посылаются команды для GPU)</p>
|
||
<p>Они возвращают очередь, выполняющую код (функцию/процедуру соотвественно) на CPU.<br />
|
||
Пример применения приведён
|
||
<a path="../Возвращаемое значение очередей/">
|
||
на странице выше
|
||
</a>
|
||
.</p>
|
||
<hr />
|
||
<p>Если во время выполнения очереди возникает какая-либо ошибка - весь пользовательский код,
|
||
выполняемый на CPU в этой очереди получает <code>ThreadAbortException</code>:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var t := Context.Default.BeginInvoke(
|
||
HPQ(()->
|
||
begin
|
||
try
|
||
Sleep(1000);
|
||
except
|
||
on e: Exception do lock output do
|
||
begin
|
||
Writeln('Ошибка во время выполнения первого HPQ:');
|
||
Writeln(e);
|
||
Writeln;
|
||
end;
|
||
end;
|
||
// Это никогда не выполнится, потому что
|
||
// ThreadAbortException кидает себя ещё раз в конце try
|
||
Writeln(1);
|
||
end)
|
||
*
|
||
HPQ(()->
|
||
begin
|
||
Sleep(500);
|
||
raise new Exception('abc');
|
||
end)
|
||
);
|
||
|
||
try
|
||
t.Wait;
|
||
except
|
||
on e: Exception do lock output do
|
||
begin
|
||
Writeln('Ошибка во время выполнения очереди:');
|
||
Writeln(e);
|
||
end;
|
||
end;
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Исключение <code>ThreadAbortException</code> во многом опасно.<br />
|
||
Подробнее можно прочитать в <a href="https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.abort?view=netframework-4.8">справке от microsoft</a>.</p>
|
||
<p>А в кратце, если в вашем коде была кинута <code>ThreadAbortException</code> - становится очень сложно сказать что-либо о его состоянии.<br />
|
||
Даже потокобезопастный код, как вызов <code>Buffer.Dispose</code>, может привести к утечкам памяти, если посреди него кинут <code>ThreadAbortException</code>.</p>
|
||
<p>Считайте получение <code>ThreadAbortException</code> критической ошибкой, после которой очень желателен перезапуск всего .exe файла.</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-18"))</script>
|
||
<div id="page-19" page_name="Комбинируя другие очереди" hidden=true>
|
||
<p>Если сложить две очереди A и B (<code>var C := A+B</code>) — получится очередь C, в которой сначала выполнится A, а затем B.<br />
|
||
Очередь C будет считаться выполненной тогда, когда выполнится очередь B.</p>
|
||
<p>Если умножить две очереди A и B (<code>var C := A*B</code>) — получится очередь C, в которой одновременно начнут выполняться A и B.<br />
|
||
Очередь C будет считаться выполненной тогда, когда обе очереди (A и B) выполнятся.</p>
|
||
<p>Как и в математике, умножение имеет бОльший приоритет чем сложение.</p>
|
||
<p>В обоих случаях очередь C будет возвращать то, что вернула очередь B. То есть если складывать и умножать много очередей - результат будет всегда возвращать то, что вернула самая последняя очередь.</p>
|
||
<p>Простейший пример:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
|
||
var q1 := HPQ(()->
|
||
begin
|
||
// lock необходим чтобы при параллельном выполнении два потока
|
||
// не пытались использовать вывод одновременно. Иначе выведет кашу
|
||
lock output do Writeln('Очередь 1 начала выполняться');
|
||
Sleep(500);
|
||
lock output do Writeln('Очередь 1 закончила выполняться');
|
||
end);
|
||
var q2 := HPQ(()->
|
||
begin
|
||
lock output do Writeln('Очередь 2 начала выполняться');
|
||
Sleep(500);
|
||
lock output do Writeln('Очередь 2 закончила выполняться');
|
||
end);
|
||
|
||
Writeln('Последовательное выполнение:');
|
||
Context.Default.SyncInvoke( q1 + q2 );
|
||
|
||
Writeln;
|
||
Writeln('Параллельное выполнение:');
|
||
Context.Default.SyncInvoke( q1 * q2 );
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Операторы += и *= также применимы к очередям.<br />
|
||
И как и для чисел - <code>A += B</code> работает как <code>A := A+B</code> (и аналогично с *=).<br />
|
||
А значит, возвращаемые типы очередей A и B должны быть одинаковыми, чтобы к ним можно было применить +=/*=.</p>
|
||
<p>Если надо сложить/умножить много очередей - лучше применять <code>CombineSyncQueue</code>/<code>CombineAsyncQueue</code> соответственно.<br />
|
||
Эти подпрограммы работают немного быстрее чем сложение и умножение, если объединять больше двух очередей.</p>
|
||
<p>Кроме того, они могут принимать ещё один параметр перед очередями:
|
||
Этот параметр позволяет указать функцию преобразования, которая использует результаты всех входных очередей:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
|
||
var q1 := HFQ( ()->1 );
|
||
var q2 := HFQ( ()->2 );
|
||
|
||
// Выводит 2, то есть только результат последней очереди
|
||
// Так сделано из за вопросов производительности
|
||
Context.Default.SyncInvoke( q1+q2 ).Println;
|
||
// Однако бывает так, что нужны результаты всех сложенных/умноженных очередей
|
||
|
||
// В таком случае надо использовать CombineSyncQueue и CombineAsyncQueue
|
||
// А точнее их перегрузку, первый параметр которой - функция преобразования
|
||
Context.Default.SyncInvoke(
|
||
CombineSyncQueue(
|
||
results->results.JoinToString, // Функция преобразования
|
||
q1, q2
|
||
)
|
||
).Println;
|
||
// Теперь выводит строку "1 2". Это то же самое, что вернёт "Arr(1,2).JoinToString"
|
||
|
||
end.
|
||
</code></pre>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-19"))</script>
|
||
<div id="page-20" page_name="Из очереди + преобразователя" hidden=true>
|
||
<p>Если надо с минимальными затратами изменить представление компилятора об очереди - лучше всего использовать <code>.Cast</code>.<br />
|
||
Но он ограничен примерно так же, как метод последовательностей <code>.Cast</code>. То есть:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
type t1 = class end;
|
||
type t2 = class(t1) end;
|
||
|
||
begin
|
||
var Q1: CommandQueue<integer> := 5;
|
||
var Q2: CommandQueueBase := Q1;
|
||
var Q3: CommandQueue<t1> := (new t2) as t1;
|
||
var Q4: CommandQueue<t1> := new t1;
|
||
var Q5: CommandQueue<t2> := new t2;
|
||
|
||
// Можно, потому что к object можно преобразовать всё
|
||
Context.Default.SyncInvoke( Q1.Cast&<object> );
|
||
|
||
// Нельзя, преобразование между 2 записями, как из integer в byte - это сложный алгоритм
|
||
Context.Default.SyncInvoke( Q1.Cast&<byte> );
|
||
|
||
// Можно, Q2 и так имеет тип CommandQueue<integer>, а значит тут Cast вернёт (Q2 as CommandQueue<integer>)
|
||
Context.Default.SyncInvoke( Q2.Cast&<integer> );
|
||
|
||
// Можно, потому что Q3 возвращает t2
|
||
Context.Default.SyncInvoke( Q3.Cast&<t2> );
|
||
|
||
// Нельзя, Q4 возвращает не t2 а t1, поэтому к t2 преобразовать не получится
|
||
Context.Default.SyncInvoke( Q4.Cast&<t2> );
|
||
|
||
// Можно, потому что t2 наследует от t1
|
||
Context.Default.SyncInvoke( Q5.Cast&<t1> );
|
||
end.
|
||
</code></pre>
|
||
<p>Ну а если эти ограничения не подходят - остаётся только <code>.ThenConvert</code>.
|
||
Он позволяет указать любой алгоритм преобразования, но работает медленнее:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var q := HFQ(()->123);
|
||
|
||
Context.Default.SyncInvoke(
|
||
q.ThenConvert(i -> i*2 )
|
||
).Println;
|
||
|
||
end.
|
||
</code></pre>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-20"))</script>
|
||
<div id="page-21" page_name="Из повторения очереди" hidden=true>
|
||
<p><del>В данный момент всё ещё не работает... Но уже совсем скоро, правда-правда!</del></p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-21"))</script>
|
||
<div id="page-22" page_name="Множественное использование очереди" hidden=true>
|
||
<p>Одну и ту же очередь можно использовать несколько раз, в том числе одновременно:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q := HPQ(()->lock output do Writeln('Q выполнилась'));
|
||
|
||
var t1 := Context.Default.BeginInvoke(Q);
|
||
var t2 := Context.Default.BeginInvoke(Q*Q);
|
||
|
||
t1.Wait;
|
||
t2.Wait;
|
||
end.
|
||
</code></pre>
|
||
<p>Но эта программа выведет "Q выполнилась" три раза, потому что при каждом упоминании - Q запускается ещё раз.</p>
|
||
<p>Это не всегда хорошо. К примеру, может быть так что <code>Q</code> содержит какой то затратный алгоритм. Или ввод значения с клавиатуры:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q := HFQ(()->
|
||
begin
|
||
lock input do
|
||
Result := ReadInteger;
|
||
end);
|
||
|
||
Context.Default.SyncInvoke(CombineAsyncQueue(
|
||
res->res,
|
||
Q,
|
||
Q.ThenConvert(i->i*i),
|
||
Q.ThenConvert(i->i*i*i)
|
||
)).Println;
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Эта программа запросит три разных значения, что не всегда то что надо.</p>
|
||
<p>Чтоб использовать результат одной очереди несколько раз - используется <code>.Multiusable</code>:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q := HFQ(()->
|
||
begin
|
||
lock input do
|
||
Result := ReadInteger;
|
||
end);
|
||
|
||
var Qs := Q.Multiusable;
|
||
|
||
Context.Default.SyncInvoke(CombineAsyncQueue(
|
||
res->res,
|
||
Qs(),
|
||
Qs().ThenConvert(i->i*i),
|
||
Qs().ThenConvert(i->i*i*i)
|
||
)).Println;
|
||
|
||
end.
|
||
</code></pre>
|
||
<p><code>.Multiusable</code> создаёт новую функцию, вызывая которую можно получить любое количество очередей,
|
||
у которых будет общий результат.</p>
|
||
<p>Каждый вызов <code>.Multiusable</code> создаёт именно новую функцию.<br />
|
||
Это значит, что если использовать результаты двух вызовов <code>.Multiusable</code> - исходная очередь выполнится два раза.</p>
|
||
<p><code>.Multiusable</code> не работает между вызовами <code>Context.BeginInvoke</code>:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q := HFQ(()->
|
||
begin
|
||
lock input do
|
||
Result := ReadInteger;
|
||
end);
|
||
|
||
var Qs := Q.Multiusable;
|
||
|
||
Context.Default.SyncInvoke( Qs() ).Println;
|
||
Context.Default.SyncInvoke( Qs().ThenConvert(i->i*i) ).Println;
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Эта программа запросит ввод два раза.</p>
|
||
<p>Если контекст у двух очередей общий - лучше объединить вызовы <code>Context.BeginInvoke</code>.
|
||
Так не только <code>.Multiusable</code> будет работать, но и выполнение будет в целом быстрее.</p>
|
||
<p>А если контекст разный - надо сохранять результат в переменную и использовать <code>Wait</code> очереди
|
||
(<a path="Из ожидания очередей">подробнее на странице ниже</a>).</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-22"))</script>
|
||
<div id="page-23" page_name="Особые .Add методы" hidden=true>
|
||
<p>Между командами для GPU (хранимыми в очередях типов <code>BufferCommandQueue</code> и <code>KernelCommandQueue</code>)
|
||
бывает надо вставить выполнение другой очереди или кода для CPU.</p>
|
||
<p>Это можно сделать, используя несколько <code>.NewQueue</code>:</p>
|
||
<pre><code>var b: Buffer;
|
||
var q0: CommandQueueBase;
|
||
...
|
||
var q :=
|
||
b.NewQueue.AddWriteValue(...) +
|
||
q0 +
|
||
HPQ(...) +
|
||
b.NewQueue.AddWriteValue(...)
|
||
;
|
||
</code></pre>
|
||
<p>Однако можно сделать и красивее:</p>
|
||
<pre><code>var b: Buffer;
|
||
var q0: CommandQueueBase;
|
||
...
|
||
var q := b.NewQueue
|
||
.AddWriteValue(...)
|
||
.AddQueue(q0)
|
||
.AddProc(...)
|
||
.AddWriteValue(...)
|
||
;
|
||
</code></pre>
|
||
<p>(вообще callback AddProc'а принимает исходный буфер/kernel параметром, что делает его более похожим на <code>.ThenConvert</code>, чем <code>HPQ</code>)</p>
|
||
<p>Эти методы не имеют незаменимых применений, но позволяют сделать код значительно читабельнее.</p>
|
||
<p>Кроме того, будьте осторожны, защита от дурака для такого случая - отсутствует:</p>
|
||
<pre><code>var q := b.NewQueue;
|
||
q.AddQueue(q);
|
||
</code></pre>
|
||
<p>Все <code>.Add*</code> методы добавляют команды в существующую очередь, а не создают новую.
|
||
Поэтому очередь, созданная предыдущим кодом при попытке выполнения начнёт циклически запускать саму себя.</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-23"))</script>
|
||
<div id="page-24" page_name="Из ожидания очередей" hidden=true>
|
||
<p>Есть всего 3 группы подпрограмм, создающих очереди для ожидания других очередей:</p>
|
||
<ol>
|
||
<li><p>Глобальные, <code>WaitFor*</code>:<br />
|
||
Ничего не делают сами, но блокируют выполнение пока указанные очереди не выполнятся.</p>
|
||
</li>
|
||
<li><p>Особые методы <code>.Add*</code> - <code>.AddWait*</code>:<br />
|
||
Как и <code>.AddQueue</code> и <code>.AddProc</code>, <code>.AddWait(...)</code> это всего лишь аналог
|
||
<code>.AddQueue(WaitFor(...))</code>, существующий только ради простоты кода.</p>
|
||
</li>
|
||
<li><p>Методы очередей, <code>.ThenWaitFor*</code>:<br />
|
||
Так же не имеет незаменимых применений, но ещё полезнее.<br />
|
||
<code>Q.ThenWaitFor(...)</code> работает как <code>Q + WaitFor(...)</code>, за исключением того, что возвращает результат <code>Q</code>.<br />
|
||
То есть, правильнее будет:</p>
|
||
<pre><code>var Qs := Q.Multiusable;
|
||
Result := Qs() + WaitFor(...) + Qs();
|
||
</code></pre>
|
||
<p>Но это уже даже в одну строчку не напишешь.</p>
|
||
</li>
|
||
</ol>
|
||
<p>В каждой из групп Wait очередей - <code>*</code> можно заменить:</p>
|
||
<ol>
|
||
<li>Ничем: Ожидание одной очереди.</li>
|
||
<li>На <code>All</code>: Ожидание всех указанных очередей.</li>
|
||
<li>На <code>Any</code>: Ожидание любой из указанных очередей (какая раньше выполнится).</li>
|
||
</ol>
|
||
<p>То есть, к примеру, <code>WaitForAny</code> создаёт очередь, которая сама ничего не выполняет, но ожидает выполнения любой из указанных очередей.</p>
|
||
<hr />
|
||
<p><code>Wait</code> очереди делают реализацию некоторых сложных деревьев выполнения очередей возможными.<br />
|
||
Примеры можно найти в папке <code>PABCWork.NET\Samples\OpenCLABC\Wait очереди</code>.</p>
|
||
<p>Но они наиболее полезны благодаря одному особенному свойству:<br />
|
||
<code>Wait</code> очереди работают даже между вызовами <code>Context.BeginInvoke</code>, в отличии от всего остального в <code>OpenCLABC</code>.</p>
|
||
<p>Это не всегда безопастно:</p>
|
||
<pre><code>Context.Default.BeginInvoke(Q1);
|
||
Context.Default.BeginInvoke(WaitFor(Q1) + Q2);
|
||
</code></pre>
|
||
<p>Проблема этого кода в том, что <code>Q1</code> может закончить выполняться ещё до того как <code>WaitFor(Q1)</code> начнёт ожидать.</p>
|
||
<p>Чтоб такое не происходило - надо всегда запускать ожидающую очередь раньше ожидаемой:</p>
|
||
<pre><code>Context.Default.BeginInvoke(WaitFor(Q1) + Q2);
|
||
Context.Default.BeginInvoke(Q1);
|
||
</code></pre>
|
||
<p>Но, как всегда, лучше, по возможности, объединять вызовы <code>Context.BeginInvoke</code>:</p>
|
||
<pre><code>Context.Default.BeginInvoke(
|
||
( Q1 ) *
|
||
( WaitFor(Q1) + Q2 )
|
||
);
|
||
</code></pre>
|
||
<p>Все ожидающие очереди начинают ожидать в самом начале вызова <code>Context.BeginInvoke</code>, перед тем как очередь начнёт выполнятся.
|
||
Поэтому если ожидающая и ожидаемая очереди находятся в общем <code>Context.BeginInvoke</code> - о их порядке можно не волноваться.</p>
|
||
<hr />
|
||
<h3>Множественное ожидание несколько раз выполнившейся очереди</h3>
|
||
<p>Когда начинает выполняться <code>Context.BeginInvoke</code> - для каждой ожидающей очереди в соответствующей
|
||
ожидаемой очереди создаётся новый счётчик для того, сколько раз ожидаемая очередь была выполнена.</p>
|
||
<p>Поэтому можно делать так:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q1 := HPQ(()->
|
||
begin
|
||
Sleep(1000);
|
||
lock output do Writeln('Выполнилась Q1');
|
||
end);
|
||
|
||
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
|
||
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
|
||
|
||
var t1 := Context.Default.BeginInvoke(
|
||
(WaitFor(Q1)+Q2) *
|
||
(WaitFor(Q1)+Q3)
|
||
);
|
||
Context.Default.SyncInvoke(Q1+Q1);
|
||
|
||
t1.Wait; // Чтобы вывести ошибки, если возникнут при выполнении
|
||
end.
|
||
</code></pre>
|
||
<p>Ну и, конечно, лучше совместить вызовы <code>Context.BeginInvoke</code>, раз контекст общий:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q1 := HPQ(()->
|
||
begin
|
||
Sleep(1000);
|
||
lock output do Writeln('Выполнилась Q1');
|
||
end);
|
||
|
||
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
|
||
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
|
||
|
||
Context.Default.SyncInvoke(
|
||
(Q1+Q1) *
|
||
(WaitFor(Q1)+Q2) *
|
||
(WaitFor(Q1)+Q3)
|
||
);
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Каждое окончание выполнения <code>Q1</code> добавляет 1 в счётчик внутри <code>Q1</code>.<br />
|
||
Каждое окончание ожидания <code>WaitFor(Q1)</code> отнимание 1 от того же счётчика.</p>
|
||
<p>Будьте осторожны, лишняя <code>Wait</code> очередь вызовет зависание:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q1 := HFQ(()->0);
|
||
|
||
var t1 := Context.Default.BeginInvoke(
|
||
WaitFor(Q1) +
|
||
WaitFor(Q1) // второй запуск Q1 никогда не произойдёт, поэтому это зависнет
|
||
);
|
||
Context.Default.SyncInvoke(Q1);
|
||
|
||
t1.Wait;
|
||
end.
|
||
</code></pre>
|
||
<hr />
|
||
<p>Cчётчик создаётся для каждой пары [ожидаемой очереди] и [вызова <code>Context.BeginInvoke</code> с ожидающей очередью].
|
||
То есть это тоже сработает:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q1 := HPQ(()->
|
||
begin
|
||
Sleep(1000);
|
||
lock output do Writeln('Выполнилась Q1');
|
||
end);
|
||
|
||
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
|
||
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
|
||
|
||
var Q4 := HPQ(()->lock output do Writeln('Выполнилась Q4'));
|
||
var Q5 := HPQ(()->lock output do Writeln('Выполнилась Q5'));
|
||
|
||
var t1 := Context.Default.BeginInvoke(
|
||
( WaitFor(Q1)+Q2 ) *
|
||
( WaitFor(Q1)+Q3 )
|
||
);
|
||
var t2 := Context.Default.BeginInvoke(
|
||
( WaitFor(Q1)+Q4 ) *
|
||
( WaitFor(Q1)+Q5 )
|
||
);
|
||
// Каждый вызов Q1 тут - активирует по 1 WaitFor(Q1) в каждом CLTask
|
||
Context.Default.SyncInvoke(Q1+Q1);
|
||
|
||
t1.Wait;
|
||
t2.Wait;
|
||
end.
|
||
</code></pre>
|
||
<p>Но каждый <code>Context.BeginInvoke</code> для ожидаемой очереди - добавляет в уже существующий счётчик:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var Q1 := HPQ(()->
|
||
begin
|
||
Sleep(1000);
|
||
lock output do Writeln('Выполнилась Q1');
|
||
end);
|
||
|
||
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
|
||
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
|
||
var Q4 := HPQ(()->lock output do Writeln('Выполнилась Q4'));
|
||
|
||
var t1 := Context.Default.BeginInvoke(
|
||
( WaitFor(Q1)+Q3 ) *
|
||
( WaitFor(Q1)+Q4 )
|
||
);
|
||
var t2 := Context.Default.BeginInvoke(WaitFor(Q1)+Q2+Q1);
|
||
Context.Default.SyncInvoke(Q1);
|
||
|
||
t1.Wait;
|
||
t2.Wait;
|
||
end.
|
||
</code></pre>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-24"))</script>
|
||
<div id="page-25" page_name="Не создавая явно" hidden=true>
|
||
<p>Передавать команды по одной, когда их несколько - ужасно не эффективно!<br />
|
||
Но нередко бывает так, что команда всего одна. Или для отладки надо одноразово выполнить одну команду.</p>
|
||
<p>Для таких случаев можно создавать очередь неявно:<br />
|
||
У каждого <code>.Add*</code> метода есть дублирующий метод в оригинальном объекте. Такие методы сами создают
|
||
новую очередь, добавляют в неё одну соответствующую команду и выполняют полученную очередь в <code>Context.Default.SyncInvoke(...)</code>.</p>
|
||
<p>Обычный код с очередями:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var b := new Buffer( 3*sizeof(integer) );
|
||
|
||
var Q_BuffWrite := b.NewQueue
|
||
.AddWriteValue(1, 0*sizeof(integer) )
|
||
.AddWriteValue(5, 1*sizeof(integer) )
|
||
.AddWriteValue(7, 2*sizeof(integer) )
|
||
;
|
||
|
||
var Q_BuffRead :=
|
||
b.NewQueue.AddGetArray1&<integer>
|
||
.ThenConvert(A->A.Println);
|
||
;
|
||
|
||
Context.Default.SyncInvoke(
|
||
Q_BuffWrite +
|
||
Q_BuffRead
|
||
);
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Он же, но с неявными очередями:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var b := new Buffer( 3*sizeof(integer) );
|
||
|
||
// Аналог Q_BuffWrite
|
||
b.WriteValue(1, 0*sizeof(integer) );
|
||
b.WriteValue(5, 1*sizeof(integer) );
|
||
b.WriteValue(7, 2*sizeof(integer) );
|
||
|
||
// Аналог Q_BuffRead
|
||
b.GetArray1&<integer>.Println;
|
||
|
||
end.
|
||
</code></pre>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-25"))</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-26" page_name="" hidden=true>
|
||
<p>Все методы создающие одну команду (<code>.Add*</code> методы и все методы неявных очередей)
|
||
могут принимать очередь вместо значения в качестве любого параметра. Но в таком случае
|
||
возвращаемый тип очереди должен совпадать с типом параметра. К примеру:</p>
|
||
<pre><code>uses OpenCLABC;
|
||
|
||
begin
|
||
var b := new Buffer(10*sizeof(integer));
|
||
// Очищаем весь буфер ноликами, чтобы не было мусора
|
||
b.FillValue(0);
|
||
|
||
var q := b.NewQueue
|
||
|
||
// Второй параметр AddWriteValue - отступ от начала буфера
|
||
// Он имеет тип integer, а значит можно передать и CommandQueue<integer>
|
||
// Таким образом, в параметр сохраняется алгоритм, а не готовое значение
|
||
// Поэтому 3 вызова ниже могут получится с 3 разными отступами
|
||
.AddWriteValue(5, HFQ(()-> Random(0,9)*sizeof(integer) ))
|
||
|
||
as CommandQueue<Buffer>;
|
||
|
||
Context.Default.SyncInvoke(q);
|
||
Context.Default.SyncInvoke(q);
|
||
Context.Default.SyncInvoke(q);
|
||
|
||
b.GetArray1&<integer>.Println;
|
||
|
||
end.
|
||
</code></pre>
|
||
<p>Все вложенные очереди начинают выполняться сразу при вызове метода <code>Context.BeginInvoke</code>, не ожидая других очередей.</p>
|
||
<p>Обычно вложенные очереди используются при вызове kernel'а,
|
||
когда надо записать что-то в буфер прямо перед вызовом kernel'а.</p>
|
||
</div>
|
||
<script>on_start_folder("Вложенные очереди", document.getElementById("page-26"))</script>
|
||
<script>on_end_folder()</script>
|
||
<script>on_end_folder()</script>
|
||
<div id="page-27" page_name="" hidden=true>
|
||
<p>В данной справке в нескольких местах можно встретить утверждения вроде</p>
|
||
<blockquote>
|
||
<p>Вызовы <code>Context.BeginInvoke</code> стоит, по возможности, объединять.</p>
|
||
</blockquote>
|
||
<p>Данный раздел подпробнее объясняет устройство модуля, что делает подобные утверждения более понятными.</p>
|
||
<p>Читать его не необходимо для написания работающего кода, но желательно для написания качественного кода.</p>
|
||
<p>Но стоит сказать заранее - это не полное объяснение внутренностей модуля. Объясняется только то, что скорее всего окажется нужным.
|
||
Если хотите ещё более полное понимание - используйте Ctrl+клик в IDE по именам, чтоб смотреть исходный код.</p>
|
||
<hr />
|
||
<h1>Страницы:</h1>
|
||
<ul>
|
||
<li><a path="Внутренности BeginInvoke"> Что делает вызов <code>BeginInvoke</code> </a></li>
|
||
</ul>
|
||
</div>
|
||
<script>on_start_folder("Оптимизация", document.getElementById("page-27"))</script>
|
||
<div id="page-28" page_name="Внутренности BeginInvoke" hidden=true>
|
||
<p>Кроме самого выполнения очередей - им так же необходима инициализация и финализация.</p>
|
||
<hr />
|
||
<h3>Инициализация</h3>
|
||
<p>Для начала, перед тем как любая из под-очередей в <code>BeginInvoke</code> начнёт выполняться - необходимо инициалировать <code>Wait</code> очереди.
|
||
Иначе у ожидаемой очереди всегда будет шанс выполнится до того как ожидающая начнёт ожидать.</p>
|
||
<p>Инициализация <code>Wait</code> очередей заключается в обходе всего дерева под-очередей. Для каждой <code>Wait</code> очереди, ожидающей уникальную очередь,
|
||
создаётся пара ключ-значения (<code>CLTask</code> и счётчик для него) и добавляется обработчик <code>CLTask.WhenDone</code> с удалением этой пары.</p>
|
||
<hr />
|
||
<h3>Запуск очередей</h3>
|
||
<p>Тоже заключается в обходе дерева под-очередей.</p>
|
||
<p>Но в этот раз очереди по которым прошлись - уже начали выполнятся, не ожидая окончания обхода всего дерева.</p>
|
||
<p>Как только этот обход закончен - метод <code>BeginInvoke</code> возвращает свой <code>CLTask</code> коду, вызвавшему его.</p>
|
||
<p>Единственное исключение - если очередь, каким то образом, уже завершила выполняться, к моменту как обход дерева закончился.<br />
|
||
Такое возможно, к примеру, если все под-очереди - константные. В таком случае финализация выполняется до выхода из <code>BeginInvoke</code>.</p>
|
||
<hr />
|
||
<h3>Финализация</h3>
|
||
<p>В основном заключается в вызове обработчиков события, как <code>CLTask.WhenDone</code>.
|
||
Обработчики созданные <code>Wait</code> очередями ничем не особенны, поэтому они тоже выполняются тут.</p>
|
||
<hr />
|
||
<p>Основное преимущество объединения вызовов <code>BeginInvoke</code> состоит в различии следующих 2 случаев:</p>
|
||
<pre><code>Context.Default.SyncInvoke(A+B);
|
||
</code></pre>
|
||
<pre><code>Context.Default.SyncInvoke(A);
|
||
Context.Default.SyncInvoke(B);
|
||
</code></pre>
|
||
<p>В первом случае пока выполнится <code>A</code> - <code>B</code> уже, скорее всего, окажется полностью запущено.
|
||
А значит как только <code>A</code> закончит выполнятся - ход выполнения перейдёт на <code>B</code>.</p>
|
||
<p>А во втором случае - между окончанием выполнения <code>A</code> и запуском <code>B</code> - будет произведено множество проверок,
|
||
а так же выходов/входов в объёмные (что значит JIT их не инлайнит) подпрограммы, как конструктор <code>CLTask</code>.</p>
|
||
<p>Да, всё это мелочи. Но нулевая задержка всегда лучше ненулевой.</p>
|
||
<hr />
|
||
<p>Ну а когда всё же приходится вызывать 2 отдельных <code>BeginInvoke</code>, к примеру на 2 разных
|
||
контекстах - можно использовать <code>Wait</code> очереди, чтоб добится того же эффекта:</p>
|
||
<pre><code>c2.BeginInvoke(WaitFor(A)+B);
|
||
c1.BeginInvoke(A);
|
||
</code></pre>
|
||
<p>Внутренние оптимизации <code>OpenCLABC</code> делают этот код практически не отличимым по скорости, от <code>BeginInvoke(A+B)</code>.</p>
|
||
<p>Единственное различие - время инициализации. Потому что <code>A</code> не запустится, пока не закончится обход дерева <code>c2.BeginInvoke</code>.</p>
|
||
</div>
|
||
<script>on_page_added(document.getElementById("page-28"))</script>
|
||
<script>on_end_folder()</script>
|
||
<script>on_end_folder()</script>
|
||
</body>
|
||
</html>
|