2
0
mirror of https://github.com/AxioDL/metaforce.git synced 2025-12-08 12:24:56 +00:00

Merge submodule contents for hecl/master

This commit is contained in:
2021-04-06 13:04:59 -04:00
140 changed files with 72497 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
set(BLENDER_SOURCES
Connection.cpp
MeshOptimizer.hpp
MeshOptimizer.cpp
SDNARead.cpp
HMDL.cpp)
hecl_add_list(Blender BLENDER_SOURCES)

File diff suppressed because it is too large Load Diff

176
hecl/lib/Blender/HMDL.cpp Normal file
View File

@@ -0,0 +1,176 @@
#include "hecl/Blender/Connection.hpp"
#include <cfloat>
#include <cmath>
#include <cstddef>
#include <vector>
#include <athena/MemoryWriter.hpp>
#undef min
#undef max
namespace hecl::blender {
atVec3f MtxVecMul4RM(const Matrix4f& mtx, const Vector3f& vec) {
atVec3f res;
athena::simd_floats resf;
athena::simd_floats mtxf[3];
for (int i = 0; i < 3; ++i)
mtx[i].simd.copy_to(mtxf[i]);
athena::simd_floats vecf(vec.val.simd);
resf[0] = mtxf[0][0] * vecf[0] + mtxf[0][1] * vecf[1] + mtxf[0][2] * vecf[2] + mtxf[0][3];
resf[1] = mtxf[1][0] * vecf[0] + mtxf[1][1] * vecf[1] + mtxf[1][2] * vecf[2] + mtxf[1][3];
resf[2] = mtxf[2][0] * vecf[0] + mtxf[2][1] * vecf[1] + mtxf[2][2] * vecf[2] + mtxf[2][3];
res.simd.copy_from(resf);
return res;
}
atVec3f MtxVecMul3RM(const Matrix4f& mtx, const Vector3f& vec) {
atVec3f res;
athena::simd_floats resf;
athena::simd_floats mtxf[3];
for (int i = 0; i < 3; ++i)
mtx[i].simd.copy_to(mtxf[i]);
athena::simd_floats vecf(vec.val.simd);
resf[0] = mtxf[0][0] * vecf[0] + mtxf[0][1] * vecf[1] + mtxf[0][2] * vecf[2];
resf[1] = mtxf[1][0] * vecf[0] + mtxf[1][1] * vecf[1] + mtxf[1][2] * vecf[2];
resf[2] = mtxf[2][0] * vecf[0] + mtxf[2][1] * vecf[1] + mtxf[2][2] * vecf[2];
res.simd.copy_from(resf);
return res;
}
HMDLBuffers Mesh::getHMDLBuffers(bool absoluteCoords, PoolSkinIndex& poolSkinIndex) const {
/* If skinned, compute max weight vec count */
size_t weightCount = 0;
for (const SkinBanks::Bank& bank : skinBanks.banks)
weightCount = std::max(weightCount, bank.m_boneIdxs.size());
size_t weightVecCount = weightCount / 4;
if (weightCount % 4)
++weightVecCount;
/* Prepare HMDL meta */
HMDLMeta metaOut;
metaOut.topology = topology;
metaOut.vertStride = (3 + 3 + colorLayerCount + uvLayerCount * 2 + weightVecCount * 4) * 4;
metaOut.colorCount = colorLayerCount;
metaOut.uvCount = uvLayerCount;
metaOut.weightCount = weightVecCount;
metaOut.bankCount = skinBanks.banks.size();
/* Total all verts from all surfaces (for ibo length) */
size_t boundVerts = 0;
for (const Surface& surf : surfaces)
boundVerts += surf.verts.size();
/* Maintain unique vert pool for VBO */
std::vector<std::pair<const Surface*, const Surface::Vert*>> vertPool;
vertPool.reserve(boundVerts);
/* Target surfaces representation */
std::vector<HMDLBuffers::Surface> outSurfaces;
outSurfaces.reserve(surfaces.size());
/* Index buffer */
std::vector<atUint32> iboData;
iboData.reserve(boundVerts);
for (const Surface& surf : surfaces) {
size_t iboStart = iboData.size();
for (const Surface::Vert& v : surf.verts) {
if (v.iPos == 0xffffffff) {
iboData.push_back(0xffffffff);
continue;
}
size_t ti = 0;
bool found = false;
for (const std::pair<const Surface*, const Surface::Vert*>& tv : vertPool) {
if (v == *tv.second && surf.skinBankIdx == tv.first->skinBankIdx) {
iboData.push_back(ti);
found = true;
break;
}
++ti;
}
if (!found) {
iboData.push_back(vertPool.size());
vertPool.emplace_back(&surf, &v);
}
}
outSurfaces.emplace_back(surf, iboStart, iboData.size() - iboStart);
}
metaOut.vertCount = vertPool.size();
metaOut.indexCount = iboData.size();
size_t vboSz = metaOut.vertCount * metaOut.vertStride;
poolSkinIndex.allocate(vertPool.size());
HMDLBuffers ret(std::move(metaOut), vboSz, iboData, std::move(outSurfaces), skinBanks);
athena::io::MemoryWriter vboW(ret.m_vboData.get(), vboSz);
uint32_t curPoolIdx = 0;
for (const std::pair<const Surface*, const Surface::Vert*>& sv : vertPool) {
const Surface& s = *sv.first;
const Surface::Vert& v = *sv.second;
if (absoluteCoords) {
atVec3f preXfPos = MtxVecMul4RM(sceneXf, pos[v.iPos]);
vboW.writeVec3fLittle(preXfPos);
atVec3f preXfNorm = MtxVecMul3RM(sceneXf, norm[v.iNorm]);
athena::simd_floats f(preXfNorm.simd * preXfNorm.simd);
float mag = f[0] + f[1] + f[2];
if (mag > FLT_EPSILON)
mag = 1.f / std::sqrt(mag);
preXfNorm.simd *= mag;
vboW.writeVec3fLittle(preXfNorm);
} else {
vboW.writeVec3fLittle(pos[v.iPos]);
vboW.writeVec3fLittle(norm[v.iNorm]);
}
for (size_t i = 0; i < colorLayerCount; ++i) {
const Vector3f& c = color[v.iColor[i]];
athena::simd_floats f(c.val.simd);
vboW.writeUByte(std::max(0, std::min(255, int(f[0] * 255))));
vboW.writeUByte(std::max(0, std::min(255, int(f[1] * 255))));
vboW.writeUByte(std::max(0, std::min(255, int(f[2] * 255))));
vboW.writeUByte(255);
}
for (size_t i = 0; i < uvLayerCount; ++i)
vboW.writeVec2fLittle(uv[v.iUv[i]]);
if (weightVecCount) {
const SkinBanks::Bank& bank = skinBanks.banks[s.skinBankIdx];
const auto& binds = skins[v.iSkin];
auto it = bank.m_boneIdxs.cbegin();
for (size_t i = 0; i < weightVecCount; ++i) {
atVec4f vec = {};
for (size_t j = 0; j < 4; ++j) {
if (it == bank.m_boneIdxs.cend())
break;
for (const SkinBind& bind : binds) {
if (!bind.valid())
break;
if (bind.vg_idx == *it) {
vec.simd[j] = bind.weight;
break;
}
}
++it;
}
vboW.writeVec4fLittle(vec);
}
}
/* mapping pool verts to skin indices */
poolSkinIndex.m_poolToSkinIndex[curPoolIdx] = sv.second->iSkin;
++curPoolIdx;
}
return ret;
}
} // namespace hecl::blender

View File

@@ -0,0 +1,439 @@
#include "MeshOptimizer.hpp"
#include <algorithm>
#include <cfloat>
#include <climits>
#include <cmath>
#include <numeric>
#include <unordered_set>
namespace hecl::blender {
logvisor::Module Log("MeshOptimizer");
template <typename T>
static void insert_unique_attr(std::unordered_map<T, uint32_t>& set, const T& attr) {
if (set.find(attr) == set.cend()) {
size_t sz = set.size();
set.insert(std::make_pair(attr, sz));
}
}
template <typename T>
static std::vector<T> sort_unordered_map(const std::unordered_map<T, uint32_t>& map) {
struct SortableIterator {
typename std::unordered_map<T, uint32_t>::const_iterator it;
bool operator<(const SortableIterator& other) const { return it->second < other.it->second; }
explicit SortableIterator(typename std::unordered_map<T, uint32_t>::const_iterator i) : it(i) {}
};
std::vector<SortableIterator> to_sort;
to_sort.reserve(map.size());
for (auto I = map.cbegin(), E = map.cend(); I != E; ++I)
to_sort.emplace_back(I);
std::sort(to_sort.begin(), to_sort.end());
std::vector<T> ret;
ret.reserve(to_sort.size());
for (const auto& sit : to_sort)
ret.push_back(sit.it->first);
return ret;
}
static bool material_is_lightmapped(const Material& mat) {
auto search = mat.iprops.find("retro_lightmapped");
if (search != mat.iprops.cend())
return search->second;
return false;
}
MeshOptimizer::Vertex::Vertex(Connection& conn) {
co.read(conn);
uint32_t skin_count;
conn._readValue(skin_count);
if (skin_count > MaxSkinEntries)
Log.report(logvisor::Fatal, FMT_STRING("Skin entry overflow {}/{}"), skin_count, MaxSkinEntries);
for (uint32_t i = 0; i < skin_count; ++i)
skin_ents[i] = Mesh::SkinBind(conn);
}
MeshOptimizer::Loop::Loop(Connection& conn, uint32_t color_count, uint32_t uv_count) {
normal.read(conn);
for (uint32_t i = 0; i < color_count; ++i)
colors[i].read(conn);
for (uint32_t i = 0; i < uv_count; ++i)
uvs[i].read(conn);
conn._readValue(vert);
conn._readValue(edge);
conn._readValue(face);
conn._readValue(link_loop_next);
conn._readValue(link_loop_prev);
conn._readValue(link_loop_radial_next);
conn._readValue(link_loop_radial_prev);
}
MeshOptimizer::Edge::Edge(Connection& conn) {
for (uint32_t i = 0; i < 2; ++i)
conn._readValue(verts[i]);
uint32_t face_count;
conn._readValue(face_count);
if (face_count > MaxLinkFaces)
Log.report(logvisor::Fatal, FMT_STRING("Face overflow {}/{}"), face_count, MaxLinkFaces);
for (uint32_t i = 0; i < face_count; ++i)
conn._readValue(link_faces[i]);
conn._readValue(is_contiguous);
}
MeshOptimizer::Face::Face(Connection& conn) {
normal.read(conn);
centroid.read(conn);
conn._readValue(material_index);
for (uint32_t i = 0; i < 3; ++i)
conn._readValue(loops[i]);
}
uint32_t MeshOptimizer::get_pos_idx(const Vertex& v) const {
auto search = b_pos.find(v.co);
if (search != b_pos.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_norm_idx(const Loop& l) const {
auto search = b_norm.find(l.normal);
if (search != b_norm.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_skin_idx(const Vertex& v) const {
auto search = b_skin.find(v.skin_ents);
if (search != b_skin.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_color_idx(const Loop& l, uint32_t cidx) const {
auto search = b_color.find(l.colors[cidx]);
if (search != b_color.cend())
return search->second;
return UINT32_MAX;
}
uint32_t MeshOptimizer::get_uv_idx(const Loop& l, uint32_t uidx) const {
if (use_luvs && uidx == 0 && material_is_lightmapped(materials[faces[l.face].material_index])) {
auto search = b_luv.find(l.uvs[0]);
if (search != b_luv.cend())
return search->second;
return UINT32_MAX;
}
auto search = b_uv.find(l.uvs[uidx]);
if (search != b_uv.cend())
return search->second;
return UINT32_MAX;
}
bool MeshOptimizer::loops_contiguous(const Loop& la, const Loop& lb) const {
if (la.vert != lb.vert)
return false;
if (get_norm_idx(la) != get_norm_idx(lb))
return false;
for (uint32_t i = 0; i < color_count; ++i)
if (get_color_idx(la, i) != get_color_idx(lb, i))
return false;
for (uint32_t i = 0; i < uv_count; ++i)
if (get_uv_idx(la, i) != get_uv_idx(lb, i))
return false;
return true;
}
bool MeshOptimizer::splitable_edge(const Edge& e) const {
if (!e.is_contiguous)
return false;
for (uint32_t vidx : e.verts) {
const Loop* found = nullptr;
for (uint32_t fidx : e.link_faces) {
for (uint32_t lidx : faces[fidx].loops) {
if (loops[lidx].vert == vidx) {
if (!found) {
found = &loops[lidx];
break;
} else {
if (!loops_contiguous(*found, loops[lidx]))
return true;
break;
}
}
}
}
}
return false;
}
void MeshOptimizer::sort_faces_by_skin_group(std::vector<uint32_t>& sfaces) const {
std::vector<uint32_t> faces_out;
faces_out.reserve(sfaces.size());
std::unordered_set<uint32_t> done_sg;
uint32_t ref_sg = UINT32_MAX;
while (faces_out.size() < sfaces.size()) {
for (uint32_t f : sfaces) {
bool found = false;
for (uint32_t l : faces[f].loops) {
uint32_t skin_idx = get_skin_idx(verts[loops[l].vert]);
if (done_sg.find(skin_idx) == done_sg.end()) {
ref_sg = skin_idx;
done_sg.insert(skin_idx);
found = true;
break;
}
}
if (found)
break;
}
for (uint32_t f : sfaces) {
if (std::find(faces_out.begin(), faces_out.end(), f) != faces_out.end())
continue;
for (uint32_t l : faces[f].loops) {
uint32_t skin_idx = get_skin_idx(verts[loops[l].vert]);
if (skin_idx == ref_sg) {
faces_out.push_back(f);
break;
}
}
}
}
sfaces = std::move(faces_out);
}
std::pair<uint32_t, uint32_t> MeshOptimizer::strip_next_loop(uint32_t prev_loop, uint32_t out_count) const {
if (out_count & 0x1) {
uint32_t radial_loop = loops[prev_loop].link_loop_radial_next;
uint32_t loop = loops[radial_loop].link_loop_prev;
return {loop, loop};
} else {
uint32_t radial_loop = loops[prev_loop].link_loop_radial_prev;
uint32_t loop = loops[radial_loop].link_loop_next;
return {loops[loop].link_loop_next, loop};
}
}
static float Magnitude(const Vector3f& v) { return std::sqrt(v.val.simd.dot3(v.val.simd)); }
static void Normalize(Vector3f& v) {
float mag = 1.f / Magnitude(v);
v.val.simd *= athena::simd<float>(mag);
}
Mesh::Surface MeshOptimizer::generate_surface(std::vector<uint32_t>& island_faces, uint32_t mat_idx) const {
Mesh::Surface ret = {};
ret.materialIdx = mat_idx;
/* Centroid of surface */
for (const auto& f : island_faces)
ret.centroid.val.simd += faces[f].centroid.val.simd;
ret.centroid.val.simd /= athena::simd<float>(island_faces.size());
/* AABB of surface */
ret.aabbMin.val.simd = athena::simd<float>(FLT_MAX);
ret.aabbMax.val.simd = athena::simd<float>(-FLT_MAX);
for (const auto& f : island_faces) {
for (const auto l : faces[f].loops) {
const Vertex& v = verts[loops[l].vert];
for (int c = 0; c < 3; ++c) {
if (v.co.val.simd[c] < ret.aabbMin.val.simd[c])
ret.aabbMin.val.simd[c] = v.co.val.simd[c];
if (v.co.val.simd[c] > ret.aabbMax.val.simd[c])
ret.aabbMax.val.simd[c] = v.co.val.simd[c];
}
}
}
/* Average normal of surface */
for (const auto& f : island_faces)
ret.reflectionNormal.val.simd += faces[f].normal.val.simd;
Normalize(ret.reflectionNormal);
/* Verts themselves */
uint32_t prev_loop_emit = UINT32_MAX;
std::vector<std::pair<std::vector<uint32_t>, std::vector<uint32_t>>> sel_lists_local;
sel_lists_local.reserve(loops.size());
while (island_faces.size()) {
sel_lists_local.clear();
for (uint32_t start_face : island_faces) {
for (uint32_t l : faces[start_face].loops) {
std::vector<uint32_t> island_local(island_faces);
uint32_t prev_loop = loops[l].link_loop_next;
uint32_t loop = loops[prev_loop].link_loop_next;
std::vector<uint32_t> sel_list;
sel_list.reserve(64);
sel_list.push_back(l);
sel_list.push_back(prev_loop);
sel_list.push_back(loop);
island_local.erase(std::find(island_local.begin(), island_local.end(), start_face));
while (true) {
const Edge& prev_edge = edges[loops[prev_loop].edge];
if (!prev_edge.is_contiguous || prev_edge.tag)
break;
std::tie(loop, prev_loop) = strip_next_loop(prev_loop, sel_list.size());
uint32_t face = loops[loop].face;
auto search = std::find(island_local.begin(), island_local.end(), face);
if (search == island_local.end())
break;
sel_list.push_back(loop);
island_local.erase(search);
}
sel_lists_local.emplace_back(std::move(sel_list), std::move(island_local));
}
}
uint32_t max_count = 0;
const std::vector<uint32_t>* max_sl = nullptr;
const std::vector<uint32_t>* max_island_faces = nullptr;
for (const auto& sl : sel_lists_local) {
if (sl.first.size() > max_count) {
max_count = sl.first.size();
max_sl = &sl.first;
max_island_faces = &sl.second;
}
}
assert(max_island_faces && "Should not be null");
assert(max_island_faces->size() < island_faces.size() && "Infinite loop condition");
island_faces = std::move(*max_island_faces);
if (prev_loop_emit != UINT32_MAX)
ret.verts.emplace_back();
for (uint32_t loop : *max_sl) {
ret.verts.emplace_back();
const auto& l = loops[loop];
auto& vert = ret.verts.back();
vert.iPos = get_pos_idx(verts[l.vert]);
vert.iNorm = get_norm_idx(l);
for (uint32_t i = 0; i < color_count; ++i)
vert.iColor[i] = get_color_idx(l, i);
for (uint32_t i = 0; i < uv_count; ++i)
vert.iUv[i] = get_uv_idx(l, i);
vert.iSkin = get_skin_idx(verts[l.vert]);
prev_loop_emit = loop;
}
}
return ret;
}
void MeshOptimizer::optimize(Mesh& mesh, int max_skin_banks) const {
mesh.topology = HMDLTopology::TriStrips;
mesh.pos = sort_unordered_map(b_pos);
mesh.norm = sort_unordered_map(b_norm);
mesh.colorLayerCount = color_count;
mesh.color = sort_unordered_map(b_color);
mesh.uvLayerCount = uv_count;
mesh.uv = sort_unordered_map(b_uv);
mesh.luv = sort_unordered_map(b_luv);
mesh.skins = sort_unordered_map(b_skin);
/* Sort materials by pass index */
std::vector<uint32_t> sorted_material_idxs(materials.size());
std::iota(sorted_material_idxs.begin(), sorted_material_idxs.end(), 0);
std::sort(sorted_material_idxs.begin(), sorted_material_idxs.end(),
[this](uint32_t a, uint32_t b) { return materials[a].passIndex < materials[b].passIndex; });
/* Generate island surfaces */
std::vector<uint32_t> mat_faces_rem, the_list;
mat_faces_rem.reserve(faces.size());
the_list.reserve(faces.size());
std::unordered_set<uint32_t> skin_slot_set;
skin_slot_set.reserve(b_skin.size());
for (uint32_t mat_idx : sorted_material_idxs) {
const auto& mat = materials[mat_idx];
mat_faces_rem.clear();
for (auto B = faces.begin(), I = B, E = faces.end(); I != E; ++I) {
if (I->material_index == mat_idx)
mat_faces_rem.push_back(I - B);
}
if (b_skin.size())
sort_faces_by_skin_group(mat_faces_rem);
size_t rem_count = mat_faces_rem.size();
while (rem_count) {
the_list.clear();
skin_slot_set.clear();
for (uint32_t& f : mat_faces_rem) {
if (f == UINT32_MAX)
continue;
if (b_skin.size()) {
bool brk = false;
for (const auto l : faces[f].loops) {
const Vertex& v = verts[loops[l].vert];
uint32_t skin_idx = get_skin_idx(v);
if (skin_slot_set.find(skin_idx) == skin_slot_set.end()) {
if (max_skin_banks > 0 && skin_slot_set.size() == size_t(max_skin_banks)) {
brk = true;
break;
}
skin_slot_set.insert(skin_idx);
}
}
if (brk)
break;
}
the_list.push_back(f);
f = UINT32_MAX;
--rem_count;
}
mesh.surfaces.push_back(generate_surface(the_list, mat_idx));
}
}
}
MeshOptimizer::MeshOptimizer(Connection& conn, const std::vector<Material>& materials, bool use_luvs)
: materials(materials), use_luvs(use_luvs) {
conn._readValue(color_count);
if (color_count > MaxColorLayers)
Log.report(logvisor::Fatal, FMT_STRING("Color layer overflow {}/{}"), color_count, MaxColorLayers);
conn._readValue(uv_count);
if (uv_count > MaxUVLayers)
Log.report(logvisor::Fatal, FMT_STRING("UV layer overflow {}/{}"), uv_count, MaxUVLayers);
/* Simultaneously load topology objects and build unique mapping indices */
uint32_t vert_count;
conn._readValue(vert_count);
verts.reserve(vert_count);
b_pos.reserve(vert_count);
b_skin.reserve(vert_count * 4);
for (uint32_t i = 0; i < vert_count; ++i) {
verts.emplace_back(conn);
insert_unique_attr(b_pos, verts.back().co);
if (verts.back().skin_ents[0].valid())
insert_unique_attr(b_skin, verts.back().skin_ents);
}
uint32_t loop_count;
conn._readValue(loop_count);
loops.reserve(loop_count);
b_norm.reserve(loop_count);
if (use_luvs) {
b_uv.reserve(std::max(int(loop_count) - 1, 0) * uv_count);
b_luv.reserve(loop_count);
} else {
b_uv.reserve(loop_count * uv_count);
}
for (uint32_t i = 0; i < loop_count; ++i) {
loops.emplace_back(conn, color_count, uv_count);
insert_unique_attr(b_norm, loops.back().normal);
for (const auto& c : loops.back().colors)
insert_unique_attr(b_color, c);
if (use_luvs && material_is_lightmapped(materials[faces[loops.back().face].material_index])) {
insert_unique_attr(b_luv, loops.back().uvs[0]);
for (auto I = std::begin(loops.back().uvs) + 1, E = std::end(loops.back().uvs); I != E; ++I)
insert_unique_attr(b_uv, *I);
} else {
for (const auto& c : loops.back().uvs)
insert_unique_attr(b_uv, c);
}
}
conn._readVector(edges);
conn._readVector(faces);
/* Cache edges that should block tristrip traversal */
for (auto& e : edges)
e.tag = splitable_edge(e);
}
}

View File

@@ -0,0 +1,117 @@
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <unordered_map>
#include <utility>
#include <vector>
#include "hecl/Blender/Connection.hpp"
namespace hecl::blender {
template <size_t S>
class IndexArray {
std::array<uint32_t, S> arr;
public:
IndexArray() { std::fill(arr.begin(), arr.end(), UINT32_MAX); }
class const_iterator {
typename std::array<uint32_t, S>::const_iterator it;
public:
explicit const_iterator(typename std::array<uint32_t, S>::const_iterator i) : it(i) {}
bool operator==(const_iterator other) const { return it == other.it; }
bool operator!=(const_iterator other) const { return it != other.it; }
const_iterator& operator++() { ++it; return *this; }
uint32_t operator*() const { return *it; }
};
const_iterator begin() const { return const_iterator(arr.cbegin()); }
const_iterator end() const {
typename std::array<uint32_t, S>::const_iterator I, E;
for (I = arr.cbegin(), E = arr.cend(); I != E && *I != UINT32_MAX; ++I) {}
return const_iterator(I);
}
uint32_t& operator[](size_t idx) { return arr[idx]; }
const uint32_t& operator[](size_t idx) const { return arr[idx]; }
static constexpr size_t capacity() { return S; }
size_t size() const { return end() - begin(); }
};
class MeshOptimizer {
static constexpr size_t MaxColorLayers = Mesh::MaxColorLayers;
static constexpr size_t MaxUVLayers = Mesh::MaxUVLayers;
static constexpr size_t MaxSkinEntries = Mesh::MaxSkinEntries;
const std::vector<Material>& materials;
bool use_luvs;
uint32_t color_count;
uint32_t uv_count;
struct Vertex {
Vector3f co = {};
std::array<Mesh::SkinBind, MaxSkinEntries> skin_ents = {};
explicit Vertex(Connection& conn);
};
std::vector<Vertex> verts;
struct Loop {
Vector3f normal = {};
std::array<Vector3f, MaxColorLayers> colors = {};
std::array<Vector2f, MaxUVLayers> uvs = {};
uint32_t vert = UINT32_MAX;
uint32_t edge = UINT32_MAX;
uint32_t face = UINT32_MAX;
uint32_t link_loop_next = UINT32_MAX;
uint32_t link_loop_prev = UINT32_MAX;
uint32_t link_loop_radial_next = UINT32_MAX;
uint32_t link_loop_radial_prev = UINT32_MAX;
explicit Loop(Connection& conn, uint32_t color_count, uint32_t uv_count);
};
std::vector<Loop> loops;
struct Edge {
static constexpr size_t MaxLinkFaces = 8;
IndexArray<2> verts;
IndexArray<MaxLinkFaces> link_faces;
bool is_contiguous = false;
bool tag = false;
explicit Edge(Connection& conn);
};
std::vector<Edge> edges;
struct Face {
Vector3f normal = {};
Vector3f centroid = {};
uint32_t material_index = UINT32_MAX;
IndexArray<3> loops;
explicit Face(Connection& conn);
};
std::vector<Face> faces;
std::unordered_map<Vector3f, uint32_t> b_pos;
std::unordered_map<Vector3f, uint32_t> b_norm;
std::unordered_map<std::array<Mesh::SkinBind, MaxSkinEntries>, uint32_t> b_skin;
std::unordered_map<Vector3f, uint32_t> b_color;
std::unordered_map<Vector2f, uint32_t> b_uv;
std::unordered_map<Vector2f, uint32_t> b_luv;
uint32_t get_pos_idx(const Vertex& v) const;
uint32_t get_norm_idx(const Loop& l) const;
uint32_t get_skin_idx(const Vertex& v) const;
uint32_t get_color_idx(const Loop& l, uint32_t cidx) const;
uint32_t get_uv_idx(const Loop& l, uint32_t uidx) const;
void sort_faces_by_skin_group(std::vector<uint32_t>& faces) const;
std::pair<uint32_t, uint32_t> strip_next_loop(uint32_t prev_loop, uint32_t out_count) const;
bool loops_contiguous(const Loop& la, const Loop& lb) const;
bool splitable_edge(const Edge& e) const;
Mesh::Surface generate_surface(std::vector<uint32_t>& island_faces, uint32_t mat_idx) const;
public:
explicit MeshOptimizer(Connection& conn, const std::vector<Material>& materials, bool use_luvs);
void optimize(Mesh& mesh, int max_skin_banks) const;
};
}

View File

@@ -0,0 +1,190 @@
#include "hecl/Blender/SDNARead.hpp"
#include <cstring>
#include <string>
#include "hecl/hecl.hpp"
#include <athena/FileReader.hpp>
#include <athena/MemoryReader.hpp>
#include <zlib.h>
namespace hecl::blender {
void SDNABlock::SDNAStruct::computeOffsets(const SDNABlock& block) {
atUint32 offset = 0;
for (SDNAField& f : fields) {
const auto& name = block.names[f.name];
f.offset = offset;
if (name.front() == '*') {
offset += 8;
} else {
atUint32 length = block.tlens[f.type];
auto bracket = name.find('[');
if (bracket != std::string::npos)
length *= strtoul(name.data() + bracket + 1, nullptr, 10);
offset += length;
}
}
}
const SDNABlock::SDNAStruct::SDNAField* SDNABlock::SDNAStruct::lookupField(const SDNABlock& block,
const char* n) const {
for (const SDNAField& field : fields) {
const auto& name = block.names[field.name];
auto bracket = name.find('[');
if (bracket != std::string::npos) {
if (!name.compare(0, bracket, n))
return &field;
} else if (name == n)
return &field;
}
return nullptr;
}
const SDNABlock::SDNAStruct* SDNABlock::lookupStruct(const char* n, atUint32& idx) const {
idx = 0;
for (const SDNAStruct& strc : strcs) {
const auto& name = types[strc.type];
if (name == n)
return &strc;
++idx;
}
return nullptr;
}
void SDNARead::enumerate(const std::function<bool(const FileBlock& block, athena::io::MemoryReader& r)>& func) const {
athena::io::MemoryReader r(m_data.data(), m_data.size());
r.seek(12);
while (r.position() < r.length()) {
FileBlock block;
block.read(r);
if (block.type == FOURCC('ENDB'))
break;
athena::io::MemoryReader r2(m_data.data() + r.position(), block.size);
if (!func(block, r2))
break;
r.seek(block.size);
}
}
SDNARead::SDNARead(SystemStringView path) {
athena::io::FileReader r(path);
if (r.hasError())
return;
atUint64 length = r.length();
char magicBuf[7];
r.readUBytesToBuf(magicBuf, 7);
r.seek(0, athena::SeekOrigin::Begin);
if (strncmp(magicBuf, "BLENDER", 7)) {
/* Try gzip decompression */
std::unique_ptr<uint8_t[]> compBuf(new uint8_t[4096]);
m_data.resize((length * 2 + 4095) & ~4095);
z_stream zstrm = {};
inflateInit2(&zstrm, 16 + MAX_WBITS);
zstrm.next_out = (Bytef*)m_data.data();
zstrm.avail_out = m_data.size();
zstrm.total_out = 0;
atUint64 rs;
while ((rs = r.readUBytesToBuf(compBuf.get(), 4096))) {
int inflateRet;
zstrm.next_in = compBuf.get();
zstrm.avail_in = rs;
while (zstrm.avail_in) {
if (!zstrm.avail_out) {
zstrm.avail_out = m_data.size();
m_data.resize(zstrm.avail_out * 2);
zstrm.next_out = (Bytef*)m_data.data() + zstrm.avail_out;
}
inflateRet = inflate(&zstrm, Z_NO_FLUSH);
if (inflateRet == Z_STREAM_END)
break;
if (inflateRet != Z_OK) {
inflateEnd(&zstrm);
m_data = std::vector<uint8_t>();
return;
}
}
if (inflateRet == Z_STREAM_END)
break;
}
inflateEnd(&zstrm);
if (strncmp((char*)m_data.data(), "BLENDER", 7)) {
m_data = std::vector<uint8_t>();
return;
}
} else {
m_data.resize(length);
r.readUBytesToBuf(m_data.data(), length);
}
enumerate([this](const FileBlock& block, athena::io::MemoryReader& r) {
if (block.type == FOURCC('DNA1')) {
m_sdnaBlock.read(r);
for (SDNABlock::SDNAStruct& s : m_sdnaBlock.strcs)
s.computeOffsets(m_sdnaBlock);
return false;
}
return true;
});
}
BlendType GetBlendType(SystemStringView path) {
SDNARead r(path);
if (!r)
return BlendType::None;
atUint32 idPropIdx;
const auto* idPropStruct = r.sdnaBlock().lookupStruct("IDProperty", idPropIdx);
if (!idPropStruct)
return BlendType::None;
const auto* typeField = idPropStruct->lookupField(r.sdnaBlock(), "type");
if (!typeField)
return BlendType::None;
atUint32 typeOffset = typeField->offset;
const auto* nameField = idPropStruct->lookupField(r.sdnaBlock(), "name");
if (!nameField)
return BlendType::None;
atUint32 nameOffset = nameField->offset;
const auto* dataField = idPropStruct->lookupField(r.sdnaBlock(), "data");
if (!dataField)
return BlendType::None;
atUint32 dataOffset = dataField->offset;
atUint32 idPropDataIdx;
const auto* idPropDataStruct = r.sdnaBlock().lookupStruct("IDPropertyData", idPropDataIdx);
if (!idPropDataStruct)
return BlendType::None;
const auto* valField = idPropDataStruct->lookupField(r.sdnaBlock(), "val");
if (!valField)
return BlendType::None;
atUint32 valOffset = dataOffset + valField->offset;
BlendType ret = BlendType::None;
r.enumerate(
[idPropIdx, typeOffset, nameOffset, valOffset, &ret](const FileBlock& block, athena::io::MemoryReader& r) {
if (block.type == FOURCC('DATA') && block.sdnaIdx == idPropIdx) {
r.seek(typeOffset, athena::SeekOrigin::Begin);
if (r.readUByte() != 1)
return true;
r.seek(nameOffset, athena::SeekOrigin::Begin);
if (r.readString() != "hecl_type")
return true;
r.seek(valOffset, athena::SeekOrigin::Begin);
ret = BlendType(r.readUint32Little());
return false;
}
return true;
});
return ret;
}
} // namespace hecl::blender

104
hecl/lib/CMakeLists.txt Normal file
View File

@@ -0,0 +1,104 @@
macro(hecl_add_list rel_path a_list)
unset(tmp_list)
foreach(path IN LISTS ${a_list})
list(APPEND tmp_list "${rel_path}/${path}")
endforeach(path)
set(${a_list} "${tmp_list}" PARENT_SCOPE)
endmacro(hecl_add_list)
add_subdirectory(Blender)
add_subdirectory(Runtime)
if(WIN32)
list(APPEND PLAT_SRCS ../include/hecl/winsupport.hpp)
endif()
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
add_definitions(-DHECL_MULTIPROCESSOR)
endif()
set(HECL_HEADERS
../include/hecl/CVar.hpp
../include/hecl/CVarManager.hpp
../include/hecl/Console.hpp
../include/hecl/CVarCommons.hpp
../include/hecl/hecl.hpp
../include/hecl/MultiProgressPrinter.hpp
../include/hecl/FourCC.hpp
../include/hecl/TypedVariant.hpp
../include/hecl/HMDLMeta.hpp
../include/hecl/Backend.hpp
../include/hecl/Blender/Connection.hpp
../include/hecl/Blender/SDNARead.hpp
../include/hecl/Blender/Token.hpp
../include/hecl/SteamFinder.hpp
../include/hecl/Database.hpp
../include/hecl/Runtime.hpp
../include/hecl/ClientProcess.hpp
../include/hecl/SystemChar.hpp
../include/hecl/BitVector.hpp
../include/hecl/MathExtras.hpp
../include/hecl/UniformBufferPool.hpp
../include/hecl/VertexBufferPool.hpp
../include/hecl/PipelineBase.hpp
../include/hecl/Pipeline.hpp
../include/hecl/Compilers.hpp)
set(COMMON_SOURCES
hecl.cpp
MultiProgressPrinter.cpp
Project.cpp
ProjectPath.cpp
HumanizeNumber.cpp
CVar.cpp
CVarCommons.cpp
CVarManager.cpp
Console.cpp
ClientProcess.cpp
SteamFinder.cpp
WideStringConvert.cpp
Compilers.cpp
Pipeline.cpp)
if(UNIX)
list(APPEND PLAT_SRCS closefrom.c)
endif()
add_library(hecl-full
${FRONTEND_SOURCES}
${RUNTIME_SOURCES}
${BLENDER_SOURCES}
${COMMON_SOURCES}
${HECL_HEADERS}
${PLAT_SRCS})
target_include_directories(hecl-full PUBLIC ../include)
target_link_libraries(hecl-full PUBLIC ${HECL_APPLICATION_REPS_TARGETS_LIST}
hecl-blender-addon boo athena-core logvisor)
target_atdna(hecl-full atdna_HMDLMeta_full.cpp ../include/hecl/HMDLMeta.hpp)
target_atdna(hecl-full atdna_CVar_full.cpp ../include/hecl/CVar.hpp)
target_atdna(hecl-full atdna_SDNARead_full.cpp ../include/hecl/Blender/SDNARead.hpp)
add_library(hecl-light
${RUNTIME_SOURCES}
${COMMON_SOURCES}
${HECL_HEADERS}
${PLAT_SRCS})
target_include_directories(hecl-light PUBLIC ../include)
target_link_libraries(hecl-light PUBLIC ${HECL_APPLICATION_REPS_TARGETS_LIST} boo athena-core logvisor)
target_atdna(hecl-light atdna_HMDLMeta_light.cpp ../include/hecl/HMDLMeta.hpp)
target_atdna(hecl-light atdna_CVar_light.cpp ../include/hecl/CVar.hpp)
add_library(hecl-compilers Compilers.cpp WideStringConvert.cpp)
get_target_property(BOO_INCLUDES boo INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(hecl-compilers PUBLIC ../include ${BOO_INCLUDES})
target_link_libraries(hecl-compilers PUBLIC athena-core logvisor xxhash
glslang OSDependent OGLCompiler SPIRV glslang-default-resource-limits)
if(COMMAND add_sanitizers)
add_sanitizers(hecl-full)
add_sanitizers(hecl-light)
add_sanitizers(hecl-compilers)
endif()
if(WINDOWS_STORE)
set_property(TARGET hecl-full PROPERTY VS_WINRT_COMPONENT TRUE)
endif()

556
hecl/lib/CVar.cpp Normal file
View File

@@ -0,0 +1,556 @@
#include "hecl/CVar.hpp"
#include <sstream>
#include "hecl/CVarManager.hpp"
#include "hecl/hecl.hpp"
#include <athena/Utility.hpp>
namespace hecl {
extern CVar* com_developer;
extern CVar* com_enableCheats;
using namespace std::literals;
CVar::CVar(std::string_view name, std::string_view value, std::string_view help, CVar::EFlags flags)
: CVar(name, help, EType::Literal) {
fromLiteral(value);
init(flags);
}
CVar::CVar(std::string_view name, const atVec2f& value, std::string_view help, EFlags flags)
: CVar(name, help, EType::Vec2f) {
fromVec2f(value);
init(flags);
}
CVar::CVar(std::string_view name, const atVec2d& value, std::string_view help, EFlags flags)
: CVar(name, help, EType::Vec2d) {
fromVec2d(value);
init(flags);
}
CVar::CVar(std::string_view name, const atVec3f& value, std::string_view help, EFlags flags)
: CVar(name, help, EType::Vec3f) {
fromVec3f(value);
init(flags, false);
}
CVar::CVar(std::string_view name, const atVec3d& value, std::string_view help, EFlags flags)
: CVar(name, help, EType::Vec3d) {
fromVec3d(value);
init(flags, false);
}
CVar::CVar(std::string_view name, const atVec4f& value, std::string_view help, EFlags flags)
: CVar(name, help, EType::Vec4f) {
fromVec4f(value);
init(flags, false);
}
CVar::CVar(std::string_view name, const atVec4d& value, std::string_view help, EFlags flags)
: CVar(name, help, EType::Vec4d) {
fromVec4d(value);
init(flags, false);
}
CVar::CVar(std::string_view name, double value, std::string_view help, EFlags flags) : CVar(name, help, EType::Real) {
fromReal(value);
init(flags);
}
CVar::CVar(std::string_view name, bool value, std::string_view help, CVar::EFlags flags)
: CVar(name, help, EType::Boolean) {
fromBoolean(value);
init(flags);
}
CVar::CVar(std::string_view name, int32_t value, std::string_view help, CVar::EFlags flags)
: CVar(name, help, EType::Signed) {
fromInteger(value);
init(flags);
}
CVar::CVar(std::string_view name, uint32_t value, std::string_view help, CVar::EFlags flags)
: CVar(name, help, EType::Unsigned) {
fromInteger(value);
init(flags);
}
std::string CVar::help() const {
return m_help + (m_defaultValue.empty() ? "" : "\ndefault: " + m_defaultValue) + (isReadOnly() ? " [ReadOnly]" : "");
}
atVec2f CVar::toVec2f(bool* isValid) const {
if (m_type != EType::Vec2f) {
if (isValid != nullptr)
*isValid = false;
return atVec2f{};
}
if (isValid != nullptr)
*isValid = true;
atVec2f vec{};
athena::simd_floats f;
std::sscanf(m_value.c_str(), "%g %g", &f[0], &f[1]);
vec.simd.copy_from(f);
return vec;
}
atVec2d CVar::toVec2d(bool* isValid) const {
if (m_type != EType::Vec2d) {
if (isValid != nullptr)
*isValid = false;
return atVec2d{};
}
if (isValid != nullptr)
*isValid = true;
atVec2d vec{};
athena::simd_doubles f;
std::sscanf(m_value.c_str(), "%lg %lg", &f[0], &f[1]);
vec.simd.copy_from(f);
return vec;
}
atVec3f CVar::toVec3f(bool* isValid) const {
if (m_type != EType::Vec3f) {
if (isValid != nullptr)
*isValid = false;
return atVec3f{};
}
if (isValid != nullptr)
*isValid = true;
atVec3f vec{};
athena::simd_floats f;
std::sscanf(m_value.c_str(), "%g %g %g", &f[0], &f[1], &f[2]);
vec.simd.copy_from(f);
return vec;
}
atVec3d CVar::toVec3d(bool* isValid) const {
if (m_type != EType::Vec3d) {
if (isValid != nullptr)
*isValid = false;
return atVec3d{};
}
if (isValid != nullptr)
*isValid = true;
atVec3d vec{};
athena::simd_doubles f;
std::sscanf(m_value.c_str(), "%lg %lg %lg", &f[0], &f[1], &f[2]);
vec.simd.copy_from(f);
return vec;
}
atVec4f CVar::toVec4f(bool* isValid) const {
if (m_type != EType::Vec4f) {
if (isValid != nullptr)
*isValid = false;
return atVec4f{};
}
if (isValid != nullptr)
*isValid = true;
atVec4f vec{};
athena::simd_floats f;
std::sscanf(m_value.c_str(), "%g %g %g %g", &f[0], &f[1], &f[2], &f[3]);
vec.simd.copy_from(f);
return vec;
}
atVec4d CVar::toVec4d(bool* isValid) const {
if (m_type != EType::Vec4d) {
if (isValid != nullptr)
*isValid = false;
return atVec4d{};
}
if (isValid != nullptr)
*isValid = true;
atVec4d vec{};
athena::simd_doubles f;
std::sscanf(m_value.c_str(), "%lg %lg %lg %lg", &f[0], &f[1], &f[2], &f[3]);
vec.simd.copy_from(f);
return vec;
}
double CVar::toReal(bool* isValid) const {
if (m_type != EType::Real) {
if (isValid)
*isValid = false;
return 0.0f;
}
if (isValid != nullptr)
*isValid = true;
return strtod(m_value.c_str(), nullptr);
}
bool CVar::toBoolean(bool* isValid) const {
if (m_type != EType::Boolean) {
if (isValid)
*isValid = false;
return false;
}
if (isValid != nullptr)
*isValid = true;
return athena::utility::parseBool(m_value);
}
int32_t CVar::toSigned(bool* isValid) const {
if (m_type != EType::Signed && m_type != EType::Unsigned) {
if (isValid)
*isValid = false;
return 0;
}
if (isValid != nullptr)
*isValid = true;
return strtol(m_value.c_str(), nullptr, 0);
}
uint32_t CVar::toUnsigned(bool* isValid) const {
if (m_type != EType::Signed && m_type != EType::Unsigned) {
if (isValid)
*isValid = false;
return 0;
}
if (isValid != nullptr)
*isValid = true;
return strtoul(m_value.c_str(), nullptr, 0);
}
std::string CVar::toLiteral(bool* isValid) const {
if (m_type != EType::Literal && (com_developer && com_developer->toBoolean())) {
if (isValid != nullptr)
*isValid = false;
} else if (isValid != nullptr) {
*isValid = true;
}
// Even if it's not a literal, it's still safe to return
return m_value;
}
std::wstring CVar::toWideLiteral(bool* isValid) const {
if (m_type != EType::Literal && (com_developer && com_developer->toBoolean())) {
if (isValid != nullptr)
*isValid = false;
} else if (isValid != nullptr) {
*isValid = true;
}
// Even if it's not a literal, it's still safe to return
return hecl::UTF8ToWide(m_value);
}
bool CVar::fromVec2f(const atVec2f& val) {
if (!safeToModify(EType::Vec2f))
return false;
athena::simd_floats f(val.simd);
m_value.assign(fmt::format(FMT_STRING("{} {}"), f[0], f[1]));
m_flags |= EFlags::Modified;
return true;
}
bool CVar::fromVec2d(const atVec2d& val) {
if (!safeToModify(EType::Vec2d))
return false;
athena::simd_doubles f(val.simd);
m_value.assign(fmt::format(FMT_STRING("{} {}"), f[0], f[1]));
m_flags |= EFlags::Modified;
return true;
}
bool CVar::fromVec3f(const atVec3f& val) {
if (!safeToModify(EType::Vec3f))
return false;
athena::simd_floats f(val.simd);
m_value.assign(fmt::format(FMT_STRING("{} {} {}"), f[0], f[1], f[2]));
m_flags |= EFlags::Modified;
return true;
}
bool CVar::fromVec3d(const atVec3d& val) {
if (!safeToModify(EType::Vec3d))
return false;
athena::simd_doubles f(val.simd);
m_value.assign(fmt::format(FMT_STRING("{} {} {}"), f[0], f[1], f[2]));
m_flags |= EFlags::Modified;
return true;
}
bool CVar::fromVec4f(const atVec4f& val) {
if (!safeToModify(EType::Vec4f))
return false;
athena::simd_floats f(val.simd);
m_value.assign(fmt::format(FMT_STRING("{} {} {} {}"), f[0], f[1], f[2], f[3]));
m_flags |= EFlags::Modified;
return true;
}
bool CVar::fromVec4d(const atVec4d& val) {
if (!safeToModify(EType::Vec4d))
return false;
athena::simd_doubles f(val.simd);
m_value.assign(fmt::format(FMT_STRING("{} {} {} {}"), f[0], f[1], f[2], f[3]));
m_flags |= EFlags::Modified;
return true;
}
bool CVar::fromReal(double val) {
if (!safeToModify(EType::Real))
return false;
m_value.assign(fmt::format(FMT_STRING("{}"), val));
setModified();
return true;
}
bool CVar::fromBoolean(bool val) {
if (!safeToModify(EType::Boolean))
return false;
if (val)
m_value = "true"sv;
else
m_value = "false"sv;
setModified();
return true;
}
bool CVar::fromInteger(int32_t val) {
if ((com_developer && com_enableCheats) && (!com_developer->toBoolean() || !com_enableCheats->toBoolean()) &&
isCheat())
return false;
// We'll accept both signed an unsigned input
if (m_type != EType::Signed && m_type != EType::Unsigned)
return false;
if (isReadOnly() && (com_developer && !com_developer->toBoolean()))
return false;
// Properly format based on signedness
m_value = fmt::format(FMT_STRING("{}"), (m_type == EType::Signed ? val : static_cast<uint32_t>(val)));
setModified();
return true;
}
bool CVar::fromInteger(uint32_t val) {
if ((com_developer && com_enableCheats) && (!com_developer->toBoolean() || !com_enableCheats->toBoolean()) &&
isCheat())
return false;
// We'll accept both signed an unsigned input
if (m_type != EType::Signed && m_type != EType::Unsigned)
return false;
if (isReadOnly() && (com_developer && !com_developer->toBoolean()))
return false;
// Properly format based on signedness
m_value = fmt::format(FMT_STRING("{}"), (m_type == EType::Unsigned ? val : static_cast<int32_t>(val)));
setModified();
return true;
}
bool CVar::fromLiteral(std::string_view val) {
if (!safeToModify(EType::Literal))
return false;
m_value.assign(val);
setModified();
return true;
}
bool CVar::fromLiteral(std::wstring_view val) {
if (!safeToModify(EType::Literal))
return false;
m_value.assign(hecl::WideToUTF8(val));
setModified();
return true;
}
bool CVar::fromLiteralToType(std::string_view val) {
if (!safeToModify(m_type) || !isValidInput(val))
return false;
m_value = val;
setModified();
return true;
}
bool CVar::fromLiteralToType(std::wstring_view val) {
return fromLiteralToType(hecl::WideToUTF8(val));
}
bool CVar::isModified() const { return True(m_flags & EFlags::Modified); }
bool CVar::modificationRequiresRestart() const { return True(m_flags & EFlags::ModifyRestart); }
bool CVar::isReadOnly() const { return True(m_flags & EFlags::ReadOnly); }
bool CVar::isCheat() const { return True(m_flags & EFlags::Cheat); }
bool CVar::isHidden() const { return True(m_flags & EFlags::Hidden); }
bool CVar::isArchive() const { return True(m_flags & EFlags::Archive); }
bool CVar::isInternalArchivable() const { return True(m_flags & EFlags::InternalArchivable); }
bool CVar::isColor() const {
return True(m_flags & EFlags::Color) &&
(m_type == EType::Vec3f || m_type == EType::Vec3d || m_type == EType::Vec3f || m_type == EType::Vec4d);
}
bool CVar::isNoDeveloper() const { return True(m_flags & EFlags::NoDeveloper); }
bool CVar::wasDeserialized() const { return m_wasDeserialized; }
bool CVar::hasDefaultValue() const { return m_defaultValue == m_value; }
void CVar::clearModified() {
if (!modificationRequiresRestart())
m_flags &= ~EFlags::Modified;
}
void CVar::setModified() { m_flags |= EFlags::Modified; }
void CVar::unlock() {
if (isReadOnly() && !m_unlocked) {
m_oldFlags = m_flags;
m_flags &= ~EFlags::ReadOnly;
m_unlocked = true;
}
}
void CVar::lock() {
if (!isReadOnly() && m_unlocked) {
m_flags = m_oldFlags;
m_unlocked = false;
}
}
void CVar::dispatch() {
for (const ListenerFunc& listen : m_listeners)
listen(this);
}
bool isReal(std::string_view v) {
char* p;
std::strtod(v.data(), &p);
return *p == 0;
}
bool isReal(const std::vector<std::string>& v) {
for (auto& s : v) {
if (!isReal(s))
return false;
}
return true;
}
bool CVar::isValidInput(std::string_view input) const {
std::vector<std::string> parts = athena::utility::split(input, ' ');
char* p;
switch(m_type) {
case EType::Boolean: {
bool valid = false;
athena::utility::parseBool(input, &valid);
return valid;
}
case EType::Signed:
std::strtol(input.data(), &p, 0);
return p == nullptr;
case EType::Unsigned:
std::strtoul(input.data(), &p, 0);
return p == nullptr;
case EType::Real: {
bool size = parts.size() == 1;
bool ret = isReal(input);
return ret && size;
}
case EType::Literal:
return true;
case EType::Vec2f:
case EType::Vec2d:
return parts.size() == 2 && isReal(parts);
case EType::Vec3f:
case EType::Vec3d:
return parts.size() == 3 && isReal(parts);
case EType::Vec4f:
case EType::Vec4d:
return parts.size() == 4 && isReal(parts);
}
return false;
}
bool CVar::isValidInput(std::wstring_view input) const {
return isValidInput(hecl::WideToUTF8(input));
}
bool CVar::safeToModify(EType type) const {
// Are we NoDevelper?
if (isNoDeveloper())
return false;
// Are we a cheat?
if (isCheat() && (com_developer && com_enableCheats) &&
(!com_developer->toBoolean() || !com_enableCheats->toBoolean()))
return false;
// Are we read only?
if (isReadOnly() && (com_developer && !com_developer->toBoolean()))
return false;
return m_type == type;
}
void CVar::init(EFlags flags, bool removeColor) {
m_defaultValue = m_value;
m_flags = flags;
if (removeColor) {
// If the user specifies color, we don't want it
m_flags &= ~EFlags::Color;
}
}
} // namespace hecl

78
hecl/lib/CVarCommons.cpp Normal file
View File

@@ -0,0 +1,78 @@
#include "hecl/CVarCommons.hpp"
namespace hecl {
namespace {
CVarCommons* m_instance = nullptr;
}
CVarCommons::CVarCommons(CVarManager& manager) : m_mgr(manager) {
m_fullscreen = m_mgr.findOrMakeCVar("fullscreen"sv, "Start in fullscreen"sv, false,
hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive);
m_graphicsApi = m_mgr.findOrMakeCVar("graphicsApi"sv, "API to use for rendering graphics"sv, DEFAULT_GRAPHICS_API,
hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive |
hecl::CVar::EFlags::ModifyRestart);
m_drawSamples = m_mgr.findOrMakeCVar("drawSamples"sv, "Number of MSAA samples to use for render targets"sv, 1,
hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive |
hecl::CVar::EFlags::ModifyRestart);
m_texAnisotropy = m_mgr.findOrMakeCVar(
"texAnisotropy"sv, "Number of anisotropic samples to use for sampling textures"sv, 1,
hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ModifyRestart);
m_deepColor = m_mgr.findOrMakeCVar("deepColor"sv, "Allow framebuffer with color depth greater-then 24-bits"sv, false,
hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive |
hecl::CVar::EFlags::ModifyRestart);
m_variableDt = m_mgr.findOrMakeCVar(
"variableDt", "Enable variable delta time (experimental)", false,
(hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ModifyRestart));
m_debugOverlayPlayerInfo = m_mgr.findOrMakeCVar(
"debugOverlay.playerInfo"sv, "Displays information about the player, such as location and orientation"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayWorldInfo = m_mgr.findOrMakeCVar(
"debugOverlay.worldInfo"sv, "Displays information about the current world, such as world asset ID, and areaId"sv,
false, hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayAreaInfo = m_mgr.findOrMakeCVar(
"debugOverlay.areaInfo"sv,
"Displays information about the current area, such as asset ID, object/layer counts, and active layer bits"sv,
false, hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayShowFrameCounter =
m_mgr.findOrMakeCVar("debugOverlay.showFrameCounter"sv, "Displays the current frame index"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayShowFramerate =
m_mgr.findOrMakeCVar("debugOverlay.showFramerate"sv, "Displays the current framerate"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayShowInGameTime =
m_mgr.findOrMakeCVar("debugOverlay.showInGameTime"sv, "Displays the current in game time"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayShowRoomTimer = m_mgr.findOrMakeCVar(
"debugOverlay.showRoomTimer", "Displays the current/last room timers in seconds and frames"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayShowResourceStats = m_mgr.findOrMakeCVar(
"debugOverlay.showResourceStats"sv, "Displays the current live resource object and token counts"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugOverlayShowRandomStats = m_mgr.findOrMakeCVar(
"debugOverlay.showRandomStats", "Displays the current number of random calls per frame"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugToolDrawAiPath =
m_mgr.findOrMakeCVar("debugTool.drawAiPath", "Draws the selected paths of any AI in the room"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugToolDrawLighting = m_mgr.findOrMakeCVar("debugTool.drawLighting", "Draws the lighting setup in a room"sv,
false, hecl::CVar::EFlags::Game | hecl::CVar::EFlags::ReadOnly);
m_debugToolDrawCollisionActors =
m_mgr.findOrMakeCVar("debugTool.drawCollisionActors", "Draws the collision actors for enemies and objects"sv,
false, hecl::CVar::EFlags::Game | hecl::CVar::EFlags::ReadOnly);
m_debugToolDrawMazePath =
m_mgr.findOrMakeCVar("debugTool.drawMazePath", "Draws the maze path in Dynamo"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_debugToolDrawPlatformCollision =
m_mgr.findOrMakeCVar("debugTool.drawPlatformCollision", "Draws the bounding boxes of platforms"sv, false,
hecl::CVar::EFlags::Game | hecl::CVar::EFlags::Archive | hecl::CVar::EFlags::ReadOnly);
m_logFile = m_mgr.findOrMakeCVar("logFile"sv, "Any log prints will be stored to this file upon exit"sv, "app.log"sv,
hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive |
hecl::CVar::EFlags::ModifyRestart);
m_instance = this;
}
CVarCommons* CVarCommons::instance() { return m_instance; }
} // namespace hecl

366
hecl/lib/CVarManager.cpp Normal file
View File

@@ -0,0 +1,366 @@
#include "hecl/CVarManager.hpp"
#include <algorithm>
#include <memory>
#include <regex>
#include "hecl/Console.hpp"
#include "hecl/hecl.hpp"
#include "hecl/Runtime.hpp"
#include <athena/FileWriter.hpp>
#include <athena/Utility.hpp>
namespace hecl {
CVar* com_developer = nullptr;
CVar* com_configfile = nullptr;
CVar* com_enableCheats = nullptr;
CVar* com_cubemaps = nullptr;
static const std::regex cmdLineRegex("\\+([\\w\\.]+)=([\\w\\.\\-]+)");
static const std::regex cmdLineRegexNoValue("\\+([\\w\\.]+)");
CVarManager* CVarManager::m_instance = nullptr;
static logvisor::Module CVarLog("CVarManager");
CVarManager::CVarManager(hecl::Runtime::FileStoreManager& store, bool useBinary)
: m_store(store), m_useBinary(useBinary) {
m_instance = this;
com_configfile =
newCVar("config", "File to store configuration", std::string("config"),
CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::NoDeveloper | CVar::EFlags::Hidden);
com_developer = newCVar("developer", "Enables developer mode", false,
(CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable));
com_enableCheats = newCVar(
"cheats", "Enable cheats", false,
(CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::Hidden | CVar::EFlags::InternalArchivable));
com_cubemaps = newCVar("cubemaps", "Enable cubemaps", false,
(CVar::EFlags::Game | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable));
}
CVarManager::~CVarManager() {}
CVar* CVarManager::registerCVar(std::unique_ptr<CVar>&& cvar) {
std::string tmp(cvar->name());
athena::utility::tolower(tmp);
if (m_cvars.find(tmp) != m_cvars.end()) {
return nullptr;
}
CVar* ret = cvar.get();
m_cvars.insert_or_assign(std::move(tmp), std::move(cvar));
return ret;
}
CVar* CVarManager::findCVar(std::string_view name) {
std::string lower(name);
athena::utility::tolower(lower);
auto search = m_cvars.find(lower);
if (search == m_cvars.end())
return nullptr;
return search->second.get();
}
std::vector<CVar*> CVarManager::archivedCVars() const {
std::vector<CVar*> ret;
for (const auto& pair : m_cvars)
if (pair.second->isArchive())
ret.push_back(pair.second.get());
return ret;
}
std::vector<CVar*> CVarManager::cvars(CVar::EFlags filter) const {
std::vector<CVar*> ret;
for (const auto& pair : m_cvars)
if (filter == CVar::EFlags::Any || True(pair.second->flags() & filter))
ret.push_back(pair.second.get());
return ret;
}
void CVarManager::deserialize(CVar* cvar) {
/* Make sure we're not trying to deserialize a CVar that is invalid or not exposed, unless it's been specified on the
* command line (i.e deferred) */
if (!cvar) {
return;
}
/* First let's check for a deferred value */
std::string lowName = cvar->name().data();
athena::utility::tolower(lowName);
if (const auto iter = m_deferedCVars.find(lowName); iter != m_deferedCVars.end()) {
std::string val = std::move(iter->second);
m_deferedCVars.erase(lowName);
if (cvar->isBoolean() && val.empty()) {
// We were deferred without a value, assume true
cvar->fromBoolean(true);
} else if (!val.empty() && cvar->fromLiteralToType(val)) {
return;
}
}
/* Enforce isArchive and isInternalArchivable now that we've checked if it's been deferred */
if (!cvar->isArchive() && !cvar->isInternalArchivable()) {
return;
}
/* We were either unable to find a deferred value or got an invalid value */
#if _WIN32
hecl::SystemString filename =
hecl::SystemString(m_store.getStoreRoot()) + _SYS_STR('/') + com_configfile->toWideLiteral();
#else
hecl::SystemString filename =
hecl::SystemString(m_store.getStoreRoot()) + _SYS_STR('/') + com_configfile->toLiteral();
#endif
hecl::Sstat st;
if (m_useBinary) {
CVarContainer container;
filename += _SYS_STR(".bin");
if (hecl::Stat(filename.c_str(), &st) || !S_ISREG(st.st_mode))
return;
athena::io::FileReader reader(filename);
if (reader.isOpen())
container.read(reader);
if (container.cvars.size() > 0) {
auto serialized = std::find_if(container.cvars.begin(), container.cvars.end(),
[&cvar](const DNACVAR::CVar& c) { return c.m_name == cvar->name(); });
if (serialized != container.cvars.end()) {
DNACVAR::CVar& tmp = *serialized;
if (cvar->m_value != tmp.m_value) {
CVarUnlocker lc(cvar);
cvar->fromLiteralToType(tmp.m_value);
cvar->m_wasDeserialized = true;
}
}
}
} else {
filename += _SYS_STR(".yaml");
if (hecl::Stat(filename.c_str(), &st) || !S_ISREG(st.st_mode))
return;
athena::io::FileReader reader(filename);
if (reader.isOpen()) {
athena::io::YAMLDocReader docReader;
if (docReader.parse(&reader)) {
std::unique_ptr<athena::io::YAMLNode> root = docReader.releaseRootNode();
auto serialized = std::find_if(root->m_mapChildren.begin(), root->m_mapChildren.end(),
[&cvar](const auto& c) { return c.first == cvar->name(); });
if (serialized != root->m_mapChildren.end()) {
const std::unique_ptr<athena::io::YAMLNode>& tmp = serialized->second;
if (cvar->m_value != tmp->m_scalarString) {
CVarUnlocker lc(cvar);
cvar->fromLiteralToType(tmp->m_scalarString);
cvar->m_wasDeserialized = true;
}
}
}
}
}
}
void CVarManager::serialize() {
#if _WIN32
hecl::SystemString filename =
hecl::SystemString(m_store.getStoreRoot()) + _SYS_STR('/') + com_configfile->toWideLiteral();
#else
hecl::SystemString filename =
hecl::SystemString(m_store.getStoreRoot()) + _SYS_STR('/') + com_configfile->toLiteral();
#endif
if (m_useBinary) {
CVarContainer container;
for (const auto& pair : m_cvars) {
const auto& cvar = pair.second;
if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) {
container.cvars.push_back(*cvar);
}
}
container.cvarCount = atUint32(container.cvars.size());
filename += _SYS_STR(".bin");
athena::io::FileWriter writer(filename);
if (writer.isOpen())
container.write(writer);
} else {
filename += _SYS_STR(".yaml");
athena::io::FileReader r(filename);
athena::io::YAMLDocWriter docWriter(r.isOpen() ? &r : nullptr);
r.close();
docWriter.setStyle(athena::io::YAMLNodeStyle::Block);
for (const auto& pair : m_cvars) {
const auto& cvar = pair.second;
if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) {
docWriter.writeString(cvar->name().data(), cvar->toLiteral());
}
}
athena::io::FileWriter w(filename);
if (w.isOpen())
docWriter.finish(&w);
}
}
CVarManager* CVarManager::instance() { return m_instance; }
void CVarManager::list(Console* con, const std::vector<std::string>& /*args*/) {
for (const auto& cvar : m_cvars) {
if (!cvar.second->isHidden())
con->report(Console::Level::Info, FMT_STRING("{}: {}"), cvar.second->name(), cvar.second->help());
}
}
void CVarManager::setCVar(Console* con, const std::vector<std::string>& args) {
if (args.size() < 2) {
con->report(Console::Level::Info, FMT_STRING("Usage setCvar <cvar> <value>"));
return;
}
std::string cvName = args[0];
athena::utility::tolower(cvName);
const auto iter = m_cvars.find(cvName);
if (iter == m_cvars.end()) {
con->report(Console::Level::Error, FMT_STRING("CVar '{}' does not exist"), args[0]);
return;
}
const auto& cv = iter->second;
std::string oldVal = cv->value();
std::string value = args[1];
auto it = args.begin() + 2;
for (; it != args.end(); ++it)
value += " " + *it;
/* Check to make sure we're not redundantly assigning the value */
if (cv->value() == value)
return;
if (!cv->fromLiteralToType(value))
con->report(Console::Level::Warning, FMT_STRING("Unable to set cvar '{}' to value '{}'"), cv->name(), value);
else
con->report(Console::Level::Info, FMT_STRING("Set '{}' from '{}' to '{}'"), cv->name(), oldVal, value);
}
void CVarManager::getCVar(Console* con, const std::vector<std::string>& args) {
if (args.empty()) {
con->report(Console::Level::Info, FMT_STRING("Usage getCVar <cvar>"));
return;
}
std::string cvName = args[0];
athena::utility::tolower(cvName);
const auto iter = m_cvars.find(cvName);
if (iter == m_cvars.end()) {
con->report(Console::Level::Error, FMT_STRING("CVar '{}' does not exist"), args[0]);
return;
}
const auto& cv = iter->second;
con->report(Console::Level::Info, FMT_STRING("'{}' = '{}'"), cv->name(), cv->value());
}
void CVarManager::setDeveloperMode(bool v, bool setDeserialized) {
com_developer->unlock();
com_developer->fromBoolean(v);
if (setDeserialized)
com_developer->m_wasDeserialized = true;
com_developer->lock();
com_developer->setModified();
}
void CVarManager::setCheatsEnabled(bool v, bool setDeserialized) {
com_enableCheats->unlock();
com_enableCheats->fromBoolean(v);
if (setDeserialized)
com_enableCheats->m_wasDeserialized = true;
com_enableCheats->lock();
com_enableCheats->setModified();
}
bool CVarManager::restartRequired() const {
return std::any_of(m_cvars.cbegin(), m_cvars.cend(), [](const auto& entry) {
return entry.second->isModified() && entry.second->modificationRequiresRestart();
});
}
void CVarManager::parseCommandLine(const std::vector<SystemString>& args) {
bool oldDeveloper = suppressDeveloper();
std::string developerName(com_developer->name());
athena::utility::tolower(developerName);
for (const SystemString& arg : args) {
if (arg[0] != _SYS_STR('+')) {
continue;
}
const std::string tmp(SystemUTF8Conv(arg).str());
std::smatch matches;
std::string cvarName;
std::string cvarValue;
if (!std::regex_match(tmp, matches, cmdLineRegex)) {
if (std::regex_match(tmp, matches, cmdLineRegexNoValue)) {
// No Value was supplied, assume the player wanted to set value to 1
cvarName = matches[1].str();
} else {
continue;
}
} else {
cvarName = matches[1].str();
cvarValue = matches[2].str();
}
if (CVar* cv = findCVar(cvarName)) {
if (cvarValue.empty() && cv->isBoolean()) {
// We were set from the command line with an empty value, assume true
cv->fromBoolean(true);
} else if (!cvarValue.empty()) {
cv->fromLiteralToType(cvarValue);
}
athena::utility::tolower(cvarName);
if (developerName == cvarName)
/* Make sure we're not overriding developer mode when we restore */
oldDeveloper = com_developer->toBoolean();
} else {
/* Unable to find an existing CVar, let's defer for the time being 8 */
athena::utility::tolower(cvarName);
m_deferedCVars.insert_or_assign(std::move(cvarName), std::move(cvarValue));
}
}
restoreDeveloper(oldDeveloper);
}
bool CVarManager::suppressDeveloper() {
bool oldDeveloper = com_developer->toBoolean();
CVarUnlocker unlock(com_developer);
com_developer->fromBoolean(true);
return oldDeveloper;
}
void CVarManager::restoreDeveloper(bool oldDeveloper) {
CVarUnlocker unlock(com_developer);
com_developer->fromBoolean(oldDeveloper);
}
void CVarManager::proc() {
for (const auto& [name, cvar] : m_cvars) {
if (cvar->isModified() && !cvar->modificationRequiresRestart()) {
cvar->dispatch();
// Clear the modified flag now that we've informed everyone we've changed
cvar->clearModified();
}
}
}
} // namespace hecl

236
hecl/lib/ClientProcess.cpp Normal file
View File

@@ -0,0 +1,236 @@
#include "hecl/ClientProcess.hpp"
#include <algorithm>
#include "hecl/Blender/Connection.hpp"
#include "hecl/Database.hpp"
#include "hecl/MultiProgressPrinter.hpp"
#include <athena/FileReader.hpp>
#include <boo/IApplication.hpp>
#include <logvisor/logvisor.hpp>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <sys/wait.h>
#endif
#define HECL_MULTIPROCESSOR 1
namespace hecl {
static logvisor::Module CP_Log("hecl::ClientProcess");
ThreadLocalPtr<ClientProcess::Worker> ClientProcess::ThreadWorker;
int CpuCountOverride = 0;
void SetCpuCountOverride(int argc, const SystemChar** argv) {
bool threadArg = false;
for (int i = 1; i < argc; ++i) {
if (threadArg) {
if (int count = int(hecl::StrToUl(argv[i], nullptr, 0))) {
CpuCountOverride = count;
return;
}
}
if (!hecl::StrNCmp(argv[i], _SYS_STR("-j"), 2)) {
if (int count = int(hecl::StrToUl(argv[i] + 2, nullptr, 0))) {
CpuCountOverride = count;
return;
}
threadArg = true;
}
}
}
static int GetCPUCount() {
if (CpuCountOverride > 0) {
return CpuCountOverride;
}
int ret;
#if _WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
ret = sysinfo.dwNumberOfProcessors;
#else
ret = sysconf(_SC_NPROCESSORS_ONLN);
#endif
return ret;
}
void ClientProcess::BufferTransaction::run(blender::Token& btok) {
athena::io::FileReader r(m_path.getAbsolutePath(), 32 * 1024, false);
if (r.hasError()) {
CP_Log.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to background-buffer '{}'")), m_path.getAbsolutePath());
return;
}
if (m_offset)
r.seek(m_offset, athena::SeekOrigin::Begin);
r.readBytesToBuf(m_targetBuf, m_maxLen);
m_complete = true;
}
void ClientProcess::CookTransaction::run(blender::Token& btok) {
m_dataSpec->setThreadProject();
m_returnResult = m_parent.syncCook(m_path, m_dataSpec, btok, m_force, m_fast);
std::unique_lock lk{m_parent.m_mutex};
++m_parent.m_completedCooks;
m_parent.m_progPrinter->setMainFactor(m_parent.m_completedCooks / float(m_parent.m_addedCooks));
m_complete = true;
}
void ClientProcess::LambdaTransaction::run(blender::Token& btok) {
m_func(btok);
m_complete = true;
}
ClientProcess::Worker::Worker(ClientProcess& proc, int idx) : m_proc(proc), m_idx(idx) {
m_thr = std::thread(std::bind(&Worker::proc, this));
}
void ClientProcess::Worker::proc() {
ClientProcess::ThreadWorker.reset(this);
std::string thrName = fmt::format(FMT_STRING("HECL Worker {}"), m_idx);
logvisor::RegisterThreadName(thrName.c_str());
std::unique_lock lk{m_proc.m_mutex};
while (m_proc.m_running) {
if (!m_didInit) {
m_proc.m_initCv.notify_one();
m_didInit = true;
}
while (m_proc.m_running && m_proc.m_pendingQueue.size()) {
std::shared_ptr<Transaction> trans = std::move(m_proc.m_pendingQueue.front());
++m_proc.m_inProgress;
m_proc.m_pendingQueue.pop_front();
lk.unlock();
trans->run(m_blendTok);
lk.lock();
m_proc.m_completedQueue.push_back(std::move(trans));
--m_proc.m_inProgress;
}
m_proc.m_waitCv.notify_one();
if (!m_proc.m_running)
break;
m_proc.m_cv.wait(lk);
}
lk.unlock();
m_blendTok.shutdown();
}
ClientProcess::ClientProcess(const MultiProgressPrinter* progPrinter) : m_progPrinter(progPrinter) {
#if HECL_MULTIPROCESSOR
const int cpuCount = GetCPUCount();
#else
constexpr int cpuCount = 1;
#endif
m_workers.reserve(cpuCount);
for (int i = 0; i < cpuCount; ++i) {
std::unique_lock lk{m_mutex};
m_workers.emplace_back(*this, m_workers.size());
m_initCv.wait(lk);
}
}
std::shared_ptr<const ClientProcess::BufferTransaction> ClientProcess::addBufferTransaction(const ProjectPath& path,
void* target, size_t maxLen,
size_t offset) {
std::unique_lock lk{m_mutex};
auto ret = std::make_shared<BufferTransaction>(*this, path, target, maxLen, offset);
m_pendingQueue.emplace_back(ret);
m_cv.notify_one();
return ret;
}
std::shared_ptr<const ClientProcess::CookTransaction> ClientProcess::addCookTransaction(const hecl::ProjectPath& path,
bool force, bool fast,
Database::IDataSpec* spec) {
std::unique_lock lk{m_mutex};
auto ret = std::make_shared<CookTransaction>(*this, path, force, fast, spec);
m_pendingQueue.emplace_back(ret);
m_cv.notify_one();
++m_addedCooks;
m_progPrinter->setMainFactor(m_completedCooks / float(m_addedCooks));
return ret;
}
std::shared_ptr<const ClientProcess::LambdaTransaction>
ClientProcess::addLambdaTransaction(std::function<void(blender::Token&)>&& func) {
std::unique_lock lk{m_mutex};
auto ret = std::make_shared<LambdaTransaction>(*this, std::move(func));
m_pendingQueue.emplace_back(ret);
m_cv.notify_one();
return ret;
}
bool ClientProcess::syncCook(const hecl::ProjectPath& path, Database::IDataSpec* spec, blender::Token& btok, bool force,
bool fast) {
if (spec->canCook(path, btok)) {
const Database::DataSpecEntry* specEnt = spec->overrideDataSpec(path, spec->getDataSpecEntry());
if (specEnt) {
hecl::ProjectPath cooked = path.getCookedPath(*specEnt);
if (fast)
cooked = cooked.getWithExtension(_SYS_STR(".fast"));
cooked.makeDirChain(false);
if (force || cooked.getPathType() == ProjectPath::Type::None || path.getModtime() > cooked.getModtime()) {
if (m_progPrinter) {
hecl::SystemString str;
if (path.getAuxInfo().empty())
str = fmt::format(FMT_STRING(_SYS_STR("Cooking {}")), path.getRelativePath());
else
str = fmt::format(FMT_STRING(_SYS_STR("Cooking {}|{}")), path.getRelativePath(), path.getAuxInfo());
m_progPrinter->print(str.c_str(), nullptr, -1.f, hecl::ClientProcess::GetThreadWorkerIdx());
m_progPrinter->flush();
} else {
if (path.getAuxInfo().empty())
LogModule.report(logvisor::Info, FMT_STRING(_SYS_STR("Cooking {}")), path.getRelativePath());
else
LogModule.report(logvisor::Info, FMT_STRING(_SYS_STR("Cooking {}|{}")), path.getRelativePath(), path.getAuxInfo());
}
spec->doCook(path, cooked, false, btok, [](const SystemChar*) {});
if (m_progPrinter) {
hecl::SystemString str;
if (path.getAuxInfo().empty())
str = fmt::format(FMT_STRING(_SYS_STR("Cooked {}")), path.getRelativePath());
else
str = fmt::format(FMT_STRING(_SYS_STR("Cooked {}|{}")), path.getRelativePath(), path.getAuxInfo());
m_progPrinter->print(str.c_str(), nullptr, -1.f, hecl::ClientProcess::GetThreadWorkerIdx());
m_progPrinter->flush();
}
}
return true;
}
}
return false;
}
void ClientProcess::swapCompletedQueue(std::list<std::shared_ptr<Transaction>>& queue) {
std::unique_lock lk{m_mutex};
queue.swap(m_completedQueue);
}
void ClientProcess::waitUntilComplete() {
std::unique_lock lk{m_mutex};
while (isBusy())
m_waitCv.wait(lk);
}
void ClientProcess::shutdown() {
if (!m_running)
return;
std::unique_lock lk{m_mutex};
m_pendingQueue.clear();
m_running = false;
m_cv.notify_all();
lk.unlock();
for (Worker& worker : m_workers)
if (worker.m_thr.joinable())
worker.m_thr.join();
}
} // namespace hecl

292
hecl/lib/Compilers.cpp Normal file
View File

@@ -0,0 +1,292 @@
#include "hecl/Compilers.hpp"
#include <cstring>
#include <utility>
#include <boo/graphicsdev/GLSLMacros.hpp>
#include <logvisor/logvisor.hpp>
#include <glslang/Public/ShaderLang.h>
#include <StandAlone/ResourceLimits.h>
#include <SPIRV/GlslangToSpv.h>
#include <SPIRV/disassemble.h>
#if _WIN32
#include <d3dcompiler.h>
extern pD3DCompile D3DCompilePROC;
#endif
#if __APPLE__
#include <unistd.h>
#include <memory>
#endif
namespace hecl {
logvisor::Module Log("hecl::Compilers");
template <typename P>
struct ShaderCompiler {};
template <>
struct ShaderCompiler<PlatformType::OpenGL> {
template <typename S>
static std::pair<StageBinaryData, size_t> Compile(std::string_view text) {
std::string str = "#version 330\n";
str += BOO_GLSL_BINDING_HEAD;
str += text;
std::pair<StageBinaryData, size_t> ret(MakeStageBinaryData(str.size() + 1), str.size() + 1);
memcpy(ret.first.get(), str.data(), ret.second);
return ret;
}
};
template <>
struct ShaderCompiler<PlatformType::Vulkan> {
static constexpr EShLanguage ShaderTypes[] = {EShLangVertex, /* Invalid */
EShLangVertex, EShLangFragment, EShLangGeometry,
EShLangTessControl, EShLangTessEvaluation};
template <typename S>
static std::pair<StageBinaryData, size_t> Compile(std::string_view text) {
EShLanguage lang = ShaderTypes[int(S::Enum)];
const EShMessages messages = EShMessages(EShMsgSpvRules | EShMsgVulkanRules);
glslang::TShader shader(lang);
const char* strings[] = {"#version 330\n", BOO_GLSL_BINDING_HEAD, text.data()};
shader.setStrings(strings, 3);
if (!shader.parse(&glslang::DefaultTBuiltInResource, 110, false, messages)) {
fmt::print(FMT_STRING("{}\n"), text);
Log.report(logvisor::Fatal, FMT_STRING("unable to compile shader\n{}"), shader.getInfoLog());
return {};
}
glslang::TProgram prog;
prog.addShader(&shader);
if (!prog.link(messages)) {
Log.report(logvisor::Fatal, FMT_STRING("unable to link shader program\n{}"), prog.getInfoLog());
return {};
}
std::vector<unsigned int> out;
glslang::GlslangToSpv(*prog.getIntermediate(lang), out);
std::pair<StageBinaryData, size_t> ret(MakeStageBinaryData(out.size() * 4), out.size() * 4);
memcpy(ret.first.get(), out.data(), ret.second);
return ret;
}
};
#if _WIN32
static const char* D3DShaderTypes[] = {nullptr, "vs_5_0", "ps_5_0", "gs_5_0", "hs_5_0", "ds_5_0"};
template <>
struct ShaderCompiler<PlatformType::D3D11> {
#if _DEBUG && 0
#define BOO_D3DCOMPILE_FLAG D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL0
#else
#define BOO_D3DCOMPILE_FLAG D3DCOMPILE_OPTIMIZATION_LEVEL3
#endif
template <typename S>
static std::pair<StageBinaryData, size_t> Compile(std::string_view text) {
ComPtr<ID3DBlob> errBlob;
ComPtr<ID3DBlob> blobOut;
if (FAILED(D3DCompilePROC(text.data(), text.size(), "Boo HLSL Source", nullptr, nullptr, "main",
D3DShaderTypes[int(S::Enum)], BOO_D3DCOMPILE_FLAG, 0, &blobOut, &errBlob))) {
fmt::print(FMT_STRING("{}\n"), text);
Log.report(logvisor::Fatal, FMT_STRING("error compiling shader: {}"), (char*)errBlob->GetBufferPointer());
return {};
}
std::pair<StageBinaryData, size_t> ret(MakeStageBinaryData(blobOut->GetBufferSize()), blobOut->GetBufferSize());
memcpy(ret.first.get(), blobOut->GetBufferPointer(), blobOut->GetBufferSize());
return ret;
}
};
#endif
#if __APPLE__
template <>
struct ShaderCompiler<PlatformType::Metal> {
static bool m_didCompilerSearch;
static bool m_hasCompiler;
static bool SearchForCompiler() {
m_didCompilerSearch = true;
const char* no_metal_compiler = getenv("HECL_NO_METAL_COMPILER");
if (no_metal_compiler && atoi(no_metal_compiler))
return false;
pid_t pid = fork();
if (!pid) {
execlp("xcrun", "xcrun", "-sdk", "macosx", "metal", "--version", nullptr);
/* xcrun returns 72 if metal command not found;
* emulate that if xcrun not found */
exit(72);
}
int status, ret;
while ((ret = waitpid(pid, &status, 0)) < 0 && errno == EINTR) {}
if (ret < 0)
return false;
return WEXITSTATUS(status) == 0;
}
template <typename S>
static std::pair<StageBinaryData, size_t> Compile(std::string_view text) {
if (!m_didCompilerSearch)
m_hasCompiler = SearchForCompiler();
std::string str =
"#include <metal_stdlib>\n"
"using namespace metal;\n";
str += text;
std::pair<StageBinaryData, size_t> ret;
if (!m_hasCompiler) {
/* First byte unset to indicate source data */
ret.first = MakeStageBinaryData(str.size() + 2);
ret.first.get()[0] = 0;
ret.second = str.size() + 2;
memcpy(&ret.first.get()[1], str.data(), str.size() + 1);
} else {
int compilerOut[2];
int compilerIn[2];
pipe(compilerOut);
pipe(compilerIn);
pid_t pid = getpid();
const char* tmpdir = getenv("TMPDIR");
std::string libFile = fmt::format(FMT_STRING("{}boo_metal_shader{}.metallib"), tmpdir, pid);
/* Pipe source write to compiler */
pid_t compilerPid = fork();
if (!compilerPid) {
dup2(compilerIn[0], STDIN_FILENO);
dup2(compilerOut[1], STDOUT_FILENO);
close(compilerOut[0]);
close(compilerOut[1]);
close(compilerIn[0]);
close(compilerIn[1]);
execlp("xcrun", "xcrun", "-sdk", "macosx", "metal", "-o", "/dev/stdout", "-Wno-unused-variable",
"-Wno-unused-const-variable", "-Wno-unused-function", "-c", "-x", "metal",
#ifndef NDEBUG
"-gline-tables-only", "-MO",
#endif
"-", nullptr);
fmt::print(stderr, FMT_STRING("execlp fail {}\n"), strerror(errno));
exit(1);
}
close(compilerIn[0]);
close(compilerOut[1]);
/* Pipe compiler to linker */
pid_t linkerPid = fork();
if (!linkerPid) {
dup2(compilerOut[0], STDIN_FILENO);
close(compilerOut[0]);
close(compilerIn[1]);
/* metallib doesn't like outputting to a pipe, so temp file will have to do */
execlp("xcrun", "xcrun", "-sdk", "macosx", "metallib", "-", "-o", libFile.c_str(), nullptr);
fmt::print(stderr, FMT_STRING("execlp fail {}\n"), strerror(errno));
exit(1);
}
close(compilerOut[0]);
/* Stream in source */
const char* inPtr = str.data();
size_t inRem = str.size();
while (inRem) {
ssize_t writeRes = write(compilerIn[1], inPtr, inRem);
if (writeRes < 0) {
fmt::print(stderr, FMT_STRING("write fail {}\n"), strerror(errno));
break;
}
inPtr += writeRes;
inRem -= writeRes;
}
close(compilerIn[1]);
/* Wait for completion */
int compilerStat, linkerStat;
while (waitpid(compilerPid, &compilerStat, 0) < 0) {
if (errno == EINTR)
continue;
Log.report(logvisor::Fatal, FMT_STRING("waitpid fail {}"), strerror(errno));
return {};
}
if (WEXITSTATUS(compilerStat)) {
Log.report(logvisor::Fatal, FMT_STRING("compile fail"));
return {};
}
while (waitpid(linkerPid, &linkerStat, 0) < 0) {
if (errno == EINTR)
continue;
Log.report(logvisor::Fatal, FMT_STRING("waitpid fail {}"), strerror(errno));
return {};
}
if (WEXITSTATUS(linkerStat)) {
Log.report(logvisor::Fatal, FMT_STRING("link fail"));
return {};
}
/* Copy temp file into buffer with first byte set to indicate binary data */
FILE* fin = fopen(libFile.c_str(), "rb");
fseek(fin, 0, SEEK_END);
long libLen = ftell(fin);
fseek(fin, 0, SEEK_SET);
ret.first = MakeStageBinaryData(libLen + 1);
ret.first.get()[0] = 1;
ret.second = libLen + 1;
fread(&ret.first.get()[1], 1, libLen, fin);
fclose(fin);
unlink(libFile.c_str());
}
return ret;
}
};
bool ShaderCompiler<PlatformType::Metal>::m_didCompilerSearch = false;
bool ShaderCompiler<PlatformType::Metal>::m_hasCompiler = false;
#endif
#if HECL_NOUVEAU_NX
template <>
struct ShaderCompiler<PlatformType::NX> {
template <typename S>
static std::pair<std::shared_ptr<uint8_t[]>, size_t> Compile(std::string_view text) {
std::string str = "#version 330\n";
str += BOO_GLSL_BINDING_HEAD;
str += text;
std::pair<std::shared_ptr<uint8_t[]>, size_t> ret(new uint8_t[str.size() + 1], str.size() + 1);
memcpy(ret.first.get(), str.data(), str.size() + 1);
return ret;
}
};
#endif
template <typename P, typename S>
std::pair<StageBinaryData, size_t> CompileShader(std::string_view text) {
return ShaderCompiler<P>::template Compile<S>(text);
}
#define SPECIALIZE_COMPILE_SHADER(P) \
template std::pair<StageBinaryData, size_t> CompileShader<P, PipelineStage::Vertex>(std::string_view text); \
template std::pair<StageBinaryData, size_t> CompileShader<P, PipelineStage::Fragment>(std::string_view text); \
template std::pair<StageBinaryData, size_t> CompileShader<P, PipelineStage::Geometry>(std::string_view text); \
template std::pair<StageBinaryData, size_t> CompileShader<P, PipelineStage::Control>(std::string_view text); \
template std::pair<StageBinaryData, size_t> CompileShader<P, PipelineStage::Evaluation>(std::string_view text);
SPECIALIZE_COMPILE_SHADER(PlatformType::OpenGL)
SPECIALIZE_COMPILE_SHADER(PlatformType::Vulkan)
#if _WIN32
SPECIALIZE_COMPILE_SHADER(PlatformType::D3D11)
#endif
#if __APPLE__
SPECIALIZE_COMPILE_SHADER(PlatformType::Metal)
#endif
#if HECL_NOUVEAU_NX
SPECIALIZE_COMPILE_SHADER(PlatformType::NX)
#endif
} // namespace hecl

445
hecl/lib/Console.cpp Normal file
View File

@@ -0,0 +1,445 @@
#include <hecl/Console.hpp>
#include <cstdio>
#include <functional>
#include <string>
#include <vector>
#include "hecl/CVar.hpp"
#include "hecl/CVarManager.hpp"
#include "hecl/hecl.hpp"
#include <athena/Utility.hpp>
#include <boo/IWindow.hpp>
#include <boo/graphicsdev/IGraphicsCommandQueue.hpp>
#include <logvisor/logvisor.hpp>
namespace hecl {
Console* Console::m_instance = nullptr;
Console::Console(CVarManager* cvarMgr) : m_cvarMgr(cvarMgr), m_overwrite(false), m_cursorAtEnd(false) {
m_instance = this;
registerCommand("help", "Prints information about a given function", "<command>",
[this](Console* console, const std::vector<std::string>& args) { help(console, args); });
registerCommand("listCommands", "Prints a list of all available Commands", "",
[this](Console* console, const std::vector<std::string>& args) { listCommands(console, args); });
registerCommand("listCVars", "Lists all available CVars", "",
[this](Console* console, const std::vector<std::string>& args) { m_cvarMgr->list(console, args); });
registerCommand(
"setCVar", "Sets a given Console Variable to the specified value", "<cvar> <value>",
[this](Console* console, const std::vector<std::string>& args) { m_cvarMgr->setCVar(console, args); });
registerCommand(
"getCVar", "Prints the value stored in the specified Console Variable", "<cvar>",
[this](Console* console, const std::vector<std::string>& args) { m_cvarMgr->getCVar(console, args); });
m_conSpeed = cvarMgr->findOrMakeCVar("con_speed",
"Speed at which the console opens and closes, calculated as pixels per second",
1.f, hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive);
m_conHeight = cvarMgr->findOrMakeCVar("con_height",
"Maximum absolute height of the console, height is calculated from the top of "
"the window, expects values ranged from [0.f,1.f]",
0.5f, hecl::CVar::EFlags::System | hecl::CVar::EFlags::Archive);
}
void Console::registerCommand(std::string_view name, std::string_view helpText, std::string_view usage,
std::function<void(Console*, const std::vector<std::string>&)>&& func,
SConsoleCommand::ECommandFlags cmdFlags) {
std::string lowName{name};
athena::utility::tolower(lowName);
if (m_commands.find(lowName) != m_commands.end()) {
return;
}
m_commands.emplace(std::move(lowName), SConsoleCommand{std::string{name}, std::string{helpText}, std::string{usage},
std::move(func), cmdFlags});
}
void Console::unregisterCommand(std::string_view name) {
std::string lowName{name};
athena::utility::tolower(lowName);
const auto iter = m_commands.find(lowName);
if (iter == m_commands.end()) {
return;
}
m_commands.erase(iter);
}
void Console::executeString(const std::string& str) {
if (str.empty())
return;
/* First let's split semi-colon delimited commands */
std::vector<std::string> commands = athena::utility::split(str, ';');
if (commands.empty())
return;
for (std::string command : commands) {
command = athena::utility::trim(command);
std::vector<std::string> tmpArgs = athena::utility::split(command, ' ');
if (tmpArgs.empty())
continue;
std::vector<std::string> args;
args.reserve(tmpArgs.size());
/* detect string literals */
bool isInLiteral = false;
std::string curLiteral;
int depth = 0;
for (std::string arg : tmpArgs) {
if ((arg.front() == '\'' || arg.front() == '"')) {
++depth;
isInLiteral = true;
curLiteral += arg;
} else if ((arg.back() == '\'' || arg.back() == '"') && isInLiteral) {
--depth;
curLiteral += arg;
args.push_back(curLiteral);
if (depth <= 0) {
depth = 0;
isInLiteral = false;
curLiteral.clear();
}
} else if (isInLiteral) {
curLiteral += arg;
} else {
args.push_back(std::move(arg));
}
}
if (isInLiteral) {
if ((curLiteral.back() != '\'' && curLiteral.back() != '"') || depth > 1) {
report(Level::Warning, FMT_STRING("Unterminated string literal"));
return;
}
args.push_back(std::move(curLiteral));
}
std::string commandName = args[0];
args.erase(args.begin());
std::string lowComName = commandName;
athena::utility::tolower(lowComName);
if (const auto iter = m_commands.find(lowComName); iter != m_commands.end()) {
const SConsoleCommand& cmd = iter->second;
if (bool(cmd.m_flags & SConsoleCommand::ECommandFlags::Developer) && !com_developer->toBoolean()) {
report(Level::Error, FMT_STRING("This command can only be executed in developer mode"), commandName);
return;
}
if (bool(cmd.m_flags & SConsoleCommand::ECommandFlags::Cheat) && !com_enableCheats->toBoolean()) {
report(Level::Error, FMT_STRING("This command can only be executed with cheats enabled"), commandName);
return;
}
cmd.m_func(this, args);
} else if (const CVar* cv = m_cvarMgr->findCVar(commandName)) {
args.insert(args.begin(), std::move(commandName));
if (args.size() > 1)
m_cvarMgr->setCVar(this, args);
else
m_cvarMgr->getCVar(this, args);
} else {
report(Level::Error, FMT_STRING("'{}' is not a valid command or variable!"), commandName);
}
}
}
void Console::help(Console* /*con*/, const std::vector<std::string>& args) {
if (args.empty()) {
report(Level::Info, FMT_STRING("Expected usage: help <command>"));
return;
}
std::string cmd = args.front();
athena::utility::tolower(cmd);
auto it = m_commands.find(cmd);
if (it == m_commands.end()) {
report(Level::Error, FMT_STRING("No such command '{}'"), args.front());
return;
}
report(Level::Info, FMT_STRING("{}: {}"), it->second.m_displayName, it->second.m_helpString);
if (!it->second.m_usage.empty())
report(Level::Info, FMT_STRING("Usage: {} {}"), it->second.m_displayName, it->second.m_usage);
}
void Console::listCommands(Console* /*con*/, const std::vector<std::string>& /*args*/) {
for (const auto& comPair : m_commands)
report(Level::Info, FMT_STRING("'{}': {}"), comPair.second.m_displayName, comPair.second.m_helpString);
}
bool Console::commandExists(std::string_view cmd) const {
std::string cmdName{cmd};
athena::utility::tolower(cmdName);
return m_commands.find(cmdName) != m_commands.end();
}
void Console::vreport(Level level, fmt::string_view fmt, fmt::format_args args) {
std::string tmp = fmt::vformat(fmt, args);
std::vector<std::string> lines = athena::utility::split(tmp, '\n');
for (std::string& line : lines) {
m_log.emplace_back(std::move(line), level);
}
fmt::print(FMT_STRING("{}\n"), tmp);
}
void Console::init(boo::IWindow* window) {
m_window = window;
}
void Console::proc() {
if (m_conHeight->isModified()) {
m_cachedConHeight = float(m_conHeight->toReal());
}
if (m_conSpeed->isModified()) {
m_cachedConSpeed = float(m_conSpeed->toReal());
}
if (m_state == State::Opened) {
fmt::print(FMT_STRING("\r{} "), m_commandString);
fflush(stdout);
} else if (m_state == State::Opening)
m_state = State::Opened;
else if (m_state == State::Closing) {
m_state = State::Closed;
m_commandString.clear();
}
if (m_cursorPosition > int(m_commandString.size() - 1))
m_cursorPosition = int(m_commandString.size() - 1);
if (m_cursorPosition < -1)
m_cursorPosition = -1;
if (m_logOffset > int(m_log.size() - 1))
m_logOffset = int(m_log.size() - 1);
if (m_logOffset < 0)
m_logOffset = 0;
}
void Console::draw(boo::IGraphicsCommandQueue* /* gfxQ */) {
}
void Console::handleCharCode(unsigned long chr, boo::EModifierKey /*mod*/, bool /*repeat*/) {
if (chr == U'`' || chr == U'~') {
if (m_state == State::Closed || m_state == State::Closing)
m_state = State::Opening;
else
m_state = State::Closing;
}
if (m_state == State::Opened) {
if (!m_commandString.empty() && m_cursorPosition + 1 < int(m_commandString.size())) {
if (m_overwrite)
m_commandString[unsigned(m_cursorPosition + 1)] = char(chr);
else
m_commandString.insert(m_commandString.begin() + m_cursorPosition + 1, char(chr));
} else
m_commandString += char(chr);
++m_cursorPosition;
}
}
void Console::handleSpecialKeyDown(boo::ESpecialKey sp, boo::EModifierKey mod, bool /*repeat*/) {
if (m_state != State::Opened) {
return;
}
switch (sp) {
case boo::ESpecialKey::Insert:
m_overwrite ^= 1;
break;
case boo::ESpecialKey::Backspace: {
if (!m_commandString.empty()) {
if (True(mod & boo::EModifierKey::Ctrl)) {
size_t index = m_commandString.rfind(' ', size_t(m_cursorPosition - 1));
if (index == std::string::npos) {
m_commandString.clear();
m_cursorPosition = -1;
} else {
m_commandString.erase(index, (index - m_commandString.size()));
m_cursorPosition = int(index);
}
break;
}
if (m_cursorPosition < 0)
break;
m_commandString.erase(size_t(m_cursorPosition), 1);
--m_cursorPosition;
}
break;
}
case boo::ESpecialKey::Delete: {
if (!m_commandString.empty()) {
// Don't try to delete if the cursor is at the end of the line
if ((m_cursorPosition + 1) >= int(m_commandString.size()))
break;
if (True(mod & boo::EModifierKey::Ctrl)) {
size_t index = m_commandString.find_first_of(' ', size_t(m_cursorPosition + 1));
if (index != std::string::npos)
m_commandString.erase(size_t(m_cursorPosition + 1), index + 1);
else
m_commandString.erase(size_t(m_cursorPosition + 1), size_t(m_cursorPosition + 1) - m_commandString.size());
break;
}
m_commandString.erase(size_t(m_cursorPosition + 1), 1);
}
break;
}
case boo::ESpecialKey::PgUp: {
if (m_logOffset < int(m_log.size() - m_maxLines) - 1)
m_logOffset++;
break;
}
case boo::ESpecialKey::PgDown: {
if (m_logOffset > 0)
m_logOffset--;
break;
}
case boo::ESpecialKey::Enter: {
fmt::print(FMT_STRING("\n"));
executeString(m_commandString);
m_cursorPosition = -1;
m_commandHistory.insert(m_commandHistory.begin(), m_commandString);
m_commandString.clear();
m_showCursor = true;
m_cursorTime = 0.f;
break;
}
case boo::ESpecialKey::Left: {
if (m_cursorPosition < 0)
break;
if (True(mod & boo::EModifierKey::Ctrl))
m_cursorPosition = int(m_commandString.rfind(' ', size_t(m_cursorPosition) - 1));
else
m_cursorPosition--;
m_showCursor = true;
m_cursorTime = 0.f;
break;
}
case boo::ESpecialKey::Right: {
if (m_cursorPosition >= int(m_commandString.size() - 1))
break;
if (True(mod & boo::EModifierKey::Ctrl)) {
if (m_commandString[size_t(m_cursorPosition)] == ' ')
m_cursorPosition++;
size_t tmpPos = m_commandString.find(' ', size_t(m_cursorPosition));
if (tmpPos == std::string::npos)
m_cursorPosition = int(m_commandString.size() - 1);
else
m_cursorPosition = int(tmpPos);
} else
m_cursorPosition++;
m_showCursor = true;
m_cursorTime = 0.f;
break;
}
case boo::ESpecialKey::Up: {
if (m_commandHistory.size() == 0)
break;
m_currentCommand++;
if (m_currentCommand > int(m_commandHistory.size() - 1))
m_currentCommand = int(m_commandHistory.size() - 1);
m_commandString = m_commandHistory[size_t(m_currentCommand)];
m_cursorPosition = int(m_commandString.size() - 1);
break;
}
case boo::ESpecialKey::Down: {
if (m_commandHistory.empty())
break;
m_currentCommand--;
if (m_currentCommand >= 0) {
m_commandString = m_commandHistory[size_t(m_currentCommand)];
} else if (m_currentCommand <= -1) {
m_currentCommand = -1;
m_commandString.clear();
}
m_cursorPosition = int(m_commandString.size());
break;
}
case boo::ESpecialKey::Home:
m_cursorPosition = -1;
break;
case boo::ESpecialKey::End:
m_cursorPosition = int(m_commandString.size() - 1);
break;
default:
break;
}
}
void Console::handleSpecialKeyUp(boo::ESpecialKey /*sp*/, boo::EModifierKey /*mod*/) {}
void Console::LogVisorAdapter::report(const char* modName, logvisor::Level severity,
fmt::string_view format, fmt::format_args args) {
auto tmp = fmt::vformat(format, args);
std::vector<std::string> lines = athena::utility::split(tmp, '\n');
for (const std::string& line : lines) {
auto v = fmt::format(FMT_STRING("[{}] {}"), modName, line);
m_con->m_log.emplace_back(std::move(v), Console::Level(severity));
}
}
void Console::LogVisorAdapter::report(const char* modName, logvisor::Level severity,
fmt::wstring_view format, fmt::wformat_args args) {
auto tmp = fmt::vformat(format, args);
std::vector<std::string> lines = athena::utility::split(athena::utility::wideToUtf8(tmp), '\n');
for (const std::string& line : lines) {
auto v = fmt::format(FMT_STRING("[{}] {}"), modName, line);
m_con->m_log.emplace_back(std::move(v), Console::Level(severity));
}
}
void Console::LogVisorAdapter::reportSource(const char* modName, logvisor::Level severity, const char* file,
unsigned linenum, fmt::string_view format, fmt::format_args args) {
auto tmp = fmt::vformat(format, args);
auto v = fmt::format(FMT_STRING("[{}] {} {}:{}"), modName, tmp, file, linenum);
m_con->m_log.emplace_back(std::move(v), Console::Level(severity));
}
void Console::LogVisorAdapter::reportSource(const char* modName, logvisor::Level severity, const char* file,
unsigned linenum, fmt::wstring_view format, fmt::wformat_args args) {
auto tmp = fmt::vformat(format, args);
std::vector<std::string> lines = athena::utility::split(athena::utility::wideToUtf8(tmp), '\n');
for (const std::string& line : lines) {
auto v = fmt::format(FMT_STRING("[{}] {} {}:{}"), modName, line, file, linenum);
m_con->m_log.emplace_back(std::move(v), Console::Level(severity));
}
}
void Console::dumpLog() {
for (const auto& l : m_log) {
switch (l.second) {
case Level::Info:
fmt::print(FMT_STRING("{}\n"), l.first);
break;
case Level::Warning:
fmt::print(FMT_STRING("[Warning] {}\n"), l.first);
break;
case Level::Error:
fmt::print(FMT_STRING("[ Error ] {}\n"), l.first);
break;
case Level::Fatal:
fmt::print(FMT_STRING("[ Fatal ] {}\n"), l.first);
break;
}
}
}
void Console::RegisterLogger(Console* con) { logvisor::MainLoggers.emplace_back(new LogVisorAdapter(con)); }
Console* Console::instance() { return m_instance; }
} // namespace hecl

151
hecl/lib/HumanizeNumber.cpp Normal file
View File

@@ -0,0 +1,151 @@
#include "hecl/hecl.hpp"
#include "logvisor/logvisor.hpp"
/*
* Copyright (c) 1997, 1998, 1999, 2002 The NetBSD Foundation, Inc.
* Copyright 2013 John-Mark Gurney <jmg@FreeBSD.org>
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
* NASA Ames Research Center, by Luke Mewburn and by Tomas Svensson.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace hecl {
static logvisor::Module Log("hecl::HumanizeNumber");
static const int maxscale = 7;
std::string HumanizeNumber(int64_t quotient, size_t len, const char* suffix, int scale, HNFlags flags) {
const char *prefixes, *sep;
int i, remainder, s1, s2, sign;
int divisordeccut;
int64_t divisor, max;
size_t baselen;
/* validate args */
if (suffix == nullptr)
suffix = "";
if ((flags & HNFlags::Divisor1000) != HNFlags::None && (flags & HNFlags::IECPrefixes) != HNFlags::None)
Log.report(logvisor::Fatal, FMT_STRING("invalid flags combo"));
/* setup parameters */
remainder = 0;
if ((flags & HNFlags::IECPrefixes) != HNFlags::None) {
baselen = 2;
/*
* Use the prefixes for power of two recommended by
* the International Electrotechnical Commission
* (IEC) in IEC 80000-3 (i.e. Ki, Mi, Gi...).
*
* HN_IEC_PREFIXES implies a divisor of 1024 here
* (use of HN_DIVISOR_1000 would have triggered
* an assertion earlier).
*/
divisor = 1024;
divisordeccut = 973; /* ceil(.95 * 1024) */
if ((flags & HNFlags::B) != HNFlags::None)
prefixes = "B\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei";
else
prefixes = "\0\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei";
} else {
baselen = 1;
if ((flags & HNFlags::Divisor1000) != HNFlags::None) {
divisor = 1000;
divisordeccut = 950;
if ((flags & HNFlags::B) != HNFlags::None)
prefixes = "B\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E";
else
prefixes = "\0\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E";
} else {
divisor = 1024;
divisordeccut = 973; /* ceil(.95 * 1024) */
if ((flags & HNFlags::B) != HNFlags::None)
prefixes = "B\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E";
else
prefixes = "\0\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E";
}
}
#define SCALE2PREFIX(scale) (&prefixes[(scale)*3])
if (quotient < 0) {
sign = -1;
quotient = -quotient;
baselen += 2; /* sign, digit */
} else {
sign = 1;
baselen += 1; /* digit */
}
if ((flags & HNFlags::NoSpace) != HNFlags::None)
sep = "";
else {
sep = " ";
baselen++;
}
baselen += strlen(suffix);
/* Check if enough room for `x y' + suffix */
if (len < baselen)
Log.report(logvisor::Fatal, FMT_STRING("buffer size {} insufficient for minimum size {}"), len, baselen);
len += 1;
if ((scale & int(HNScale::AutoScale)) != 0) {
/* See if there is additional columns can be used. */
for (max = 1, i = len - baselen; i-- > 0;)
max *= 10;
/*
* Divide the number until it fits the given column.
* If there will be an overflow by the rounding below,
* divide once more.
*/
for (i = 0; (quotient >= max || (quotient == max - 1 && remainder >= divisordeccut)) && i < maxscale; i++) {
remainder = quotient % divisor;
quotient /= divisor;
}
} else {
for (i = 0; i < scale && i < maxscale; i++) {
remainder = quotient % divisor;
quotient /= divisor;
}
}
/* If a value <= 9.9 after rounding and ... */
/*
* XXX - should we make sure there is enough space for the decimal
* place and if not, don't do HN_DECIMAL?
*/
if (((quotient == 9 && remainder < divisordeccut) || quotient < 9) && i > 0 &&
(flags & HNFlags::Decimal) != HNFlags::None) {
s1 = (int)quotient + ((remainder * 10 + divisor / 2) / divisor / 10);
s2 = ((remainder * 10 + divisor / 2) / divisor) % 10;
return fmt::format(FMT_STRING("{}{}{}{}{}{}"), sign * s1, localeconv()->decimal_point, s2, sep, SCALE2PREFIX(i), suffix);
} else
return fmt::format(FMT_STRING("{}{}{}{}"), sign * (quotient + (remainder + divisor / 2) / divisor), sep,
SCALE2PREFIX(i), suffix);
}
} // namespace hecl

View File

@@ -0,0 +1,365 @@
#include "hecl/MultiProgressPrinter.hpp"
#include <algorithm>
#include <cstdio>
#include "hecl/hecl.hpp"
#define BOLD "\033[1m"
#define NORMAL "\033[0m"
#define PREV_LINE "\r\033[{:d}A"
#define HIDE_CURSOR "\033[?25l"
#define SHOW_CURSOR "\033[?25h"
#if _WIN32
#define FOREGROUND_WHITE FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
#endif
namespace hecl {
void MultiProgressPrinter::ThreadStat::print(const TermInfo& tinfo) const {
bool blocks = m_factor >= 0.f;
float factor = std::max(0.f, std::min(1.f, m_factor));
int iFactor = factor * 100.f;
int messageLen = m_message.size();
int submessageLen = m_submessage.size();
int half;
if (blocks)
half = (tinfo.width + 1) / 2 - 2;
else if (tinfo.truncate)
half = tinfo.width - 4;
else
half = messageLen;
if (half - messageLen < submessageLen - 2)
submessageLen = 0;
if (submessageLen) {
if (messageLen > half - submessageLen - 1)
fmt::print(FMT_STRING(_SYS_STR(" {:.{}}... {} ")), m_message, half - submessageLen - 4, m_submessage);
else {
fmt::print(FMT_STRING(_SYS_STR(" {}")), m_message);
for (int i = half - messageLen - submessageLen - 1; i >= 0; --i)
fmt::print(FMT_STRING(_SYS_STR(" ")));
fmt::print(FMT_STRING(_SYS_STR("{} ")), m_submessage);
}
} else {
if (messageLen > half)
fmt::print(FMT_STRING(_SYS_STR(" {:.{}}... ")), m_message, half - 3);
else {
fmt::print(FMT_STRING(_SYS_STR(" {}")), m_message);
for (int i = half - messageLen; i >= 0; --i)
fmt::print(FMT_STRING(_SYS_STR(" ")));
}
}
if (blocks) {
int rightHalf = tinfo.width - half - 4;
int nblocks = rightHalf - 7;
int filled = nblocks * factor;
int rem = nblocks - filled;
if (tinfo.xtermColor) {
fmt::print(FMT_STRING(_SYS_STR("" BOLD "{:3d}% [")), iFactor);
for (int b = 0; b < filled; ++b)
fmt::print(FMT_STRING(_SYS_STR("#")));
for (int b = 0; b < rem; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("]" NORMAL "")));
} else {
#if _WIN32
SetConsoleTextAttribute(tinfo.console, FOREGROUND_INTENSITY | FOREGROUND_WHITE);
#endif
fmt::print(FMT_STRING(_SYS_STR("{:3d}% [")), iFactor);
for (int b = 0; b < filled; ++b)
fmt::print(FMT_STRING(_SYS_STR("#")));
for (int b = 0; b < rem; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("]")));
#if _WIN32
SetConsoleTextAttribute(tinfo.console, FOREGROUND_WHITE);
#endif
}
}
}
void MultiProgressPrinter::DrawIndeterminateBar() {
int half = m_termInfo.width - 2;
int blocks = half - 2;
++m_indeterminateCounter;
if (m_indeterminateCounter <= -blocks)
m_indeterminateCounter = -blocks + 1;
else if (m_indeterminateCounter >= blocks)
m_indeterminateCounter = -blocks + 2;
int absCounter = std::abs(m_indeterminateCounter);
int pre = absCounter;
int rem = blocks - pre - 1;
if (m_termInfo.xtermColor) {
fmt::print(FMT_STRING(_SYS_STR("" BOLD " [")));
for (int b = 0; b < pre; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("#")));
for (int b = 0; b < rem; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("]" NORMAL "")));
} else {
#if _WIN32
SetConsoleTextAttribute(m_termInfo.console, FOREGROUND_INTENSITY | FOREGROUND_WHITE);
#endif
fmt::print(FMT_STRING(_SYS_STR(" [")));
for (int b = 0; b < pre; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("#")));
for (int b = 0; b < rem; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("]")));
#if _WIN32
SetConsoleTextAttribute(m_termInfo.console, FOREGROUND_WHITE);
#endif
}
}
void MultiProgressPrinter::MoveCursorUp(int n) {
if (n) {
if (m_termInfo.xtermColor) {
fmt::print(FMT_STRING(_SYS_STR("" PREV_LINE "")), n);
}
#if _WIN32
else {
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
GetConsoleScreenBufferInfo(m_termInfo.console, &consoleInfo);
consoleInfo.dwCursorPosition.X = 0;
consoleInfo.dwCursorPosition.Y -= n;
SetConsoleCursorPosition(m_termInfo.console, consoleInfo.dwCursorPosition);
}
#endif
} else {
fmt::print(FMT_STRING(_SYS_STR("\r")));
}
}
void MultiProgressPrinter::DoPrint() {
auto logLk = logvisor::LockLog();
uint64_t logCounter = logvisor::GetLogCounter();
if (logCounter != m_lastLogCounter) {
m_curThreadLines = 0;
m_lastLogCounter = logCounter;
}
#if _WIN32
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(m_termInfo.console, &cursorInfo);
cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(m_termInfo.console, &cursorInfo);
#endif
if (m_termInfo.xtermColor)
fmt::print(FMT_STRING(_SYS_STR("" HIDE_CURSOR "")));
if (m_dirty) {
m_termInfo.width = (hecl::GuiMode ? 120 : std::max(80, hecl::ConsoleWidth(&m_termInfo.truncate)));
MoveCursorUp(m_curThreadLines + m_curProgLines);
m_curThreadLines = m_curProgLines = 0;
if (m_newLineAfter) {
for (const ThreadStat& stat : m_threadStats) {
if (stat.m_active) {
stat.print(m_termInfo);
fmt::print(FMT_STRING(_SYS_STR("\n")));
++m_curThreadLines;
}
}
if (m_mainIndeterminate
#ifndef _WIN32
&& m_termInfo.xtermColor
#endif
) {
DrawIndeterminateBar();
fmt::print(FMT_STRING(_SYS_STR("\n")));
++m_curProgLines;
} else if (m_mainFactor >= 0.f) {
float factor = std::max(0.0f, std::min(1.0f, m_mainFactor));
int iFactor = factor * 100.0;
int half = m_termInfo.width - 2;
int blocks = half - 8;
int filled = blocks * factor;
int rem = blocks - filled;
if (m_termInfo.xtermColor) {
fmt::print(FMT_STRING(_SYS_STR("" BOLD " {:3d}% [")), iFactor);
for (int b = 0; b < filled; ++b)
fmt::print(FMT_STRING(_SYS_STR("#")));
for (int b = 0; b < rem; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("]" NORMAL "")));
} else {
#if _WIN32
SetConsoleTextAttribute(m_termInfo.console, FOREGROUND_INTENSITY | FOREGROUND_WHITE);
#endif
fmt::print(FMT_STRING(_SYS_STR(" {:3d}% [")), iFactor);
for (int b = 0; b < filled; ++b)
fmt::print(FMT_STRING(_SYS_STR("#")));
for (int b = 0; b < rem; ++b)
fmt::print(FMT_STRING(_SYS_STR("-")));
fmt::print(FMT_STRING(_SYS_STR("]")));
#if _WIN32
SetConsoleTextAttribute(m_termInfo.console, FOREGROUND_WHITE);
#endif
}
fmt::print(FMT_STRING(_SYS_STR("\n")));
++m_curProgLines;
}
} else if (m_latestThread != -1) {
const ThreadStat& stat = m_threadStats[m_latestThread];
stat.print(m_termInfo);
fmt::print(FMT_STRING(_SYS_STR("\r")));
}
m_dirty = false;
} else if (m_mainIndeterminate
#ifndef _WIN32
&& m_termInfo.xtermColor
#endif
) {
m_termInfo.width = (hecl::GuiMode ? 120 : std::max(80, hecl::ConsoleWidth()));
MoveCursorUp(m_curProgLines);
m_curProgLines = 0;
DrawIndeterminateBar();
fmt::print(FMT_STRING(_SYS_STR("\n")));
++m_curProgLines;
}
if (m_termInfo.xtermColor)
fmt::print(FMT_STRING(_SYS_STR("" SHOW_CURSOR "")));
fflush(stdout);
#if _WIN32
cursorInfo.bVisible = TRUE;
SetConsoleCursorInfo(m_termInfo.console, &cursorInfo);
#endif
}
void MultiProgressPrinter::LogProc() {
while (m_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (!m_dirty && !m_mainIndeterminate) {
continue;
}
std::lock_guard lk{m_logLock};
DoPrint();
}
}
MultiProgressPrinter::MultiProgressPrinter(bool activate) {
if (activate) {
/* Xterm check */
#if _WIN32
m_newLineAfter = true;
m_termInfo.console = GetStdHandle(STD_OUTPUT_HANDLE);
const char* conemuANSI = getenv("ConEmuANSI");
if (conemuANSI && !strcmp(conemuANSI, "ON"))
m_termInfo.xtermColor = true;
#else
m_newLineAfter = false;
const char* term = getenv("TERM");
if (term && !strncmp(term, "xterm", 5)) {
m_termInfo.xtermColor = true;
m_newLineAfter = true;
}
#endif
m_running = true;
m_logThread = std::thread(std::bind(&MultiProgressPrinter::LogProc, this));
}
}
MultiProgressPrinter::~MultiProgressPrinter() {
m_running = false;
if (m_logThread.joinable())
m_logThread.join();
}
void MultiProgressPrinter::print(const hecl::SystemChar* message, const hecl::SystemChar* submessage, float factor,
int threadIdx) const {
if (!m_running) {
return;
}
std::lock_guard lk{m_logLock};
if (threadIdx < 0) {
threadIdx = 0;
}
if (threadIdx >= m_threadStats.size()) {
m_threadStats.resize(threadIdx + 1);
}
ThreadStat& stat = m_threadStats[threadIdx];
if (message) {
stat.m_message = message;
} else {
stat.m_message.clear();
}
if (submessage) {
stat.m_submessage = submessage;
} else {
stat.m_submessage.clear();
}
stat.m_factor = factor;
stat.m_active = true;
m_latestThread = threadIdx;
m_dirty = true;
}
void MultiProgressPrinter::setMainFactor(float factor) const {
if (!m_running) {
return;
}
std::lock_guard lk{m_logLock};
if (!m_mainIndeterminate) {
m_dirty = true;
}
m_mainFactor = factor;
}
void MultiProgressPrinter::setMainIndeterminate(bool indeterminate) const {
if (!m_running) {
return;
}
std::lock_guard lk{m_logLock};
if (m_mainIndeterminate != indeterminate) {
m_mainIndeterminate = indeterminate;
m_dirty = true;
}
}
void MultiProgressPrinter::startNewLine() const {
if (!m_running) {
return;
}
std::lock_guard lk{m_logLock};
const_cast<MultiProgressPrinter&>(*this).DoPrint();
m_threadStats.clear();
m_latestThread = -1;
m_curThreadLines = 0;
m_mainFactor = -1.f;
auto logLk = logvisor::LockLog();
fmt::print(FMT_STRING(_SYS_STR("\n")));
}
void MultiProgressPrinter::flush() const {
std::lock_guard lk{m_logLock};
const_cast<MultiProgressPrinter&>(*this).DoPrint();
}
} // namespace hecl

167
hecl/lib/Pipeline.cpp Normal file
View File

@@ -0,0 +1,167 @@
#include "hecl/Pipeline.hpp"
#include <athena/FileReader.hpp>
#include <zlib.h>
namespace hecl {
#if HECL_RUNTIME
PipelineConverterBase* conv = nullptr;
class ShaderCacheZipStream : public athena::io::IStreamReader {
std::unique_ptr<uint8_t[]> m_compBuf;
athena::io::FileReader m_reader;
z_stream m_zstrm = {};
public:
explicit ShaderCacheZipStream(const hecl::SystemChar* path) : m_reader(path) {
if (m_reader.hasError())
return;
if (m_reader.readUint32Big() != SBIG('SHAD'))
return;
m_compBuf.reset(new uint8_t[4096]);
m_zstrm.next_in = m_compBuf.get();
m_zstrm.avail_in = 0;
inflateInit(&m_zstrm);
}
~ShaderCacheZipStream() override { inflateEnd(&m_zstrm); }
explicit operator bool() const { return m_compBuf.operator bool(); }
atUint64 readUBytesToBuf(void* buf, atUint64 len) override {
m_zstrm.next_out = (Bytef*)buf;
m_zstrm.avail_out = len;
m_zstrm.total_out = 0;
while (m_zstrm.avail_out != 0) {
if (m_zstrm.avail_in == 0) {
atUint64 readSz = m_reader.readUBytesToBuf(m_compBuf.get(), 4096);
m_zstrm.avail_in = readSz;
m_zstrm.next_in = m_compBuf.get();
}
int inflateRet = inflate(&m_zstrm, Z_NO_FLUSH);
if (inflateRet != Z_OK)
break;
}
return m_zstrm.total_out;
}
void seek(atInt64, athena::SeekOrigin) override {}
atUint64 position() const override { return 0; }
atUint64 length() const override { return 0; }
};
template <typename P, typename S>
void StageConverter<P, S>::loadFromStream(FactoryCtx& ctx, ShaderCacheZipStream& r) {
uint32_t count = r.readUint32Big();
for (uint32_t i = 0; i < count; ++i) {
uint64_t hash = r.readUint64Big();
uint32_t size = r.readUint32Big();
StageBinaryData data = MakeStageBinaryData(size);
r.readUBytesToBuf(data.get(), size);
m_stageCache.insert(std::make_pair(hash, Do<StageTargetTp>(ctx, StageBinary<P, S>(data, size))));
}
}
static boo::AdditionalPipelineInfo ReadAdditionalInfo(ShaderCacheZipStream& r) {
boo::AdditionalPipelineInfo additionalInfo;
additionalInfo.srcFac = boo::BlendFactor(r.readUint32Big());
additionalInfo.dstFac = boo::BlendFactor(r.readUint32Big());
additionalInfo.prim = boo::Primitive(r.readUint32Big());
additionalInfo.depthTest = boo::ZTest(r.readUint32Big());
additionalInfo.depthWrite = r.readBool();
additionalInfo.colorWrite = r.readBool();
additionalInfo.alphaWrite = r.readBool();
additionalInfo.culling = boo::CullMode(r.readUint32Big());
additionalInfo.patchSize = r.readUint32Big();
additionalInfo.overwriteAlpha = r.readBool();
additionalInfo.depthAttachment = r.readBool();
return additionalInfo;
}
static std::vector<boo::VertexElementDescriptor> ReadVertexFormat(ShaderCacheZipStream& r) {
std::vector<boo::VertexElementDescriptor> ret;
uint32_t count = r.readUint32Big();
ret.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
ret.emplace_back();
ret.back().semantic = boo::VertexSemantic(r.readUint32Big());
ret.back().semanticIdx = int(r.readUint32Big());
}
return ret;
}
template <typename P>
bool PipelineConverter<P>::loadFromFile(FactoryCtx& ctx, const hecl::SystemChar* path) {
ShaderCacheZipStream r(path);
if (!r)
return false;
m_vertexConverter.loadFromStream(ctx, r);
m_fragmentConverter.loadFromStream(ctx, r);
m_geometryConverter.loadFromStream(ctx, r);
m_controlConverter.loadFromStream(ctx, r);
m_evaluationConverter.loadFromStream(ctx, r);
uint32_t count = r.readUint32Big();
for (uint32_t i = 0; i < count; ++i) {
uint64_t hash = r.readUint64Big();
StageRuntimeObject<P, PipelineStage::Vertex> vertex;
StageRuntimeObject<P, PipelineStage::Fragment> fragment;
StageRuntimeObject<P, PipelineStage::Geometry> geometry;
StageRuntimeObject<P, PipelineStage::Control> control;
StageRuntimeObject<P, PipelineStage::Evaluation> evaluation;
if (uint64_t vhash = r.readUint64Big())
vertex = m_vertexConverter.m_stageCache.find(vhash)->second;
if (uint64_t fhash = r.readUint64Big())
fragment = m_fragmentConverter.m_stageCache.find(fhash)->second;
if (uint64_t ghash = r.readUint64Big())
geometry = m_geometryConverter.m_stageCache.find(ghash)->second;
if (uint64_t chash = r.readUint64Big())
control = m_controlConverter.m_stageCache.find(chash)->second;
if (uint64_t ehash = r.readUint64Big())
evaluation = m_evaluationConverter.m_stageCache.find(ehash)->second;
boo::AdditionalPipelineInfo additionalInfo = ReadAdditionalInfo(r);
std::vector<boo::VertexElementDescriptor> vtxFmt = ReadVertexFormat(r);
m_pipelineCache.insert(
std::make_pair(hash, FinalPipeline<P>(*this, ctx,
StageCollection<StageRuntimeObject<P, PipelineStage::Null>>(
vertex, fragment, geometry, control, evaluation, additionalInfo,
boo::VertexFormatInfo(vtxFmt.size(), vtxFmt.data())))));
}
return true;
}
#define SPECIALIZE_STAGE_CONVERTER(P) \
template class StageConverter<P, PipelineStage::Vertex>; \
template class StageConverter<P, PipelineStage::Fragment>; \
template class StageConverter<P, PipelineStage::Geometry>; \
template class StageConverter<P, PipelineStage::Control>; \
template class StageConverter<P, PipelineStage::Evaluation>;
#if BOO_HAS_GL
template class PipelineConverter<PlatformType::OpenGL>;
SPECIALIZE_STAGE_CONVERTER(PlatformType::OpenGL)
#endif
#if BOO_HAS_VULKAN
template class PipelineConverter<PlatformType::Vulkan>;
SPECIALIZE_STAGE_CONVERTER(PlatformType::Vulkan)
#endif
#if _WIN32
template class PipelineConverter<PlatformType::D3D11>;
SPECIALIZE_STAGE_CONVERTER(PlatformType::D3D11)
#endif
#if BOO_HAS_METAL
template class PipelineConverter<PlatformType::Metal>;
SPECIALIZE_STAGE_CONVERTER(PlatformType::Metal)
#endif
#if BOO_HAS_NX
template class PipelineConverter<PlatformType::NX>;
SPECIALIZE_STAGE_CONVERTER(PlatformType::NX)
#endif
#endif
} // namespace hecl

505
hecl/lib/Project.cpp Normal file
View File

@@ -0,0 +1,505 @@
#include <sys/stat.h>
#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
#include <system_error>
#if _WIN32
#else
#include <unistd.h>
#endif
#include "hecl/ClientProcess.hpp"
#include "hecl/Database.hpp"
#include "hecl/Blender/Connection.hpp"
#include "hecl/MultiProgressPrinter.hpp"
#include <logvisor/logvisor.hpp>
namespace hecl::Database {
logvisor::Module LogModule("hecl::Database");
constexpr hecl::FourCC HECLfcc("HECL");
/**********************************************
* Project::ConfigFile
**********************************************/
static bool CheckNewLineAdvance(std::string::const_iterator& it) {
if (*it == '\n') {
it += 1;
return true;
} else if (*it == '\r') {
if (*(it + 1) == '\n') {
it += 2;
return true;
}
it += 1;
return true;
}
return false;
}
Project::ConfigFile::ConfigFile(const Project& project, SystemStringView name, SystemStringView subdir) {
m_filepath = SystemString(project.m_rootPath.getAbsolutePath()) + subdir.data() + name.data();
}
std::vector<std::string>& Project::ConfigFile::lockAndRead() {
if (m_lockedFile != nullptr) {
return m_lines;
}
m_lockedFile = hecl::FopenUnique(m_filepath.c_str(), _SYS_STR("a+"), FileLockType::Write);
hecl::FSeek(m_lockedFile.get(), 0, SEEK_SET);
std::string mainString;
char readBuf[1024];
size_t readSz;
while ((readSz = std::fread(readBuf, 1, sizeof(readBuf), m_lockedFile.get()))) {
mainString += std::string(readBuf, readSz);
}
auto begin = mainString.cbegin();
auto end = mainString.cbegin();
m_lines.clear();
while (end != mainString.end()) {
auto origEnd = end;
if (*end == '\0') {
break;
}
if (CheckNewLineAdvance(end)) {
if (begin != origEnd) {
m_lines.emplace_back(begin, origEnd);
}
begin = end;
continue;
}
++end;
}
if (begin != end) {
m_lines.emplace_back(begin, end);
}
return m_lines;
}
void Project::ConfigFile::addLine(std::string_view line) {
if (!checkForLine(line))
m_lines.emplace_back(line);
}
void Project::ConfigFile::removeLine(std::string_view refLine) {
if (!m_lockedFile) {
LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, FMT_STRING("Project::ConfigFile::lockAndRead not yet called"));
return;
}
for (auto it = m_lines.begin(); it != m_lines.end();) {
if (*it == refLine) {
it = m_lines.erase(it);
continue;
}
++it;
}
}
bool Project::ConfigFile::checkForLine(std::string_view refLine) const {
if (!m_lockedFile) {
LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, FMT_STRING("Project::ConfigFile::lockAndRead not yet called"));
return false;
}
return std::any_of(m_lines.cbegin(), m_lines.cend(), [&refLine](const auto& line) { return line == refLine; });
}
void Project::ConfigFile::unlockAndDiscard() {
if (m_lockedFile == nullptr) {
LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, FMT_STRING("Project::ConfigFile::lockAndRead not yet called"));
return;
}
m_lines.clear();
m_lockedFile.reset();
}
bool Project::ConfigFile::unlockAndCommit() {
if (!m_lockedFile) {
LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, FMT_STRING("Project::ConfigFile::lockAndRead not yet called"));
return false;
}
const SystemString newPath = m_filepath + _SYS_STR(".part");
auto newFile = hecl::FopenUnique(newPath.c_str(), _SYS_STR("w"), FileLockType::Write);
bool fail = false;
for (const std::string& line : m_lines) {
if (std::fwrite(line.c_str(), 1, line.size(), newFile.get()) != line.size()) {
fail = true;
break;
}
if (std::fputc('\n', newFile.get()) == EOF) {
fail = true;
break;
}
}
m_lines.clear();
newFile.reset();
m_lockedFile.reset();
if (fail) {
#if HECL_UCS2
_wunlink(newPath.c_str());
#else
unlink(newPath.c_str());
#endif
return false;
} else {
#if HECL_UCS2
//_wrename(newPath.c_str(), m_filepath.c_str());
MoveFileExW(newPath.c_str(), m_filepath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
#else
rename(newPath.c_str(), m_filepath.c_str());
#endif
return true;
}
}
/**********************************************
* Project
**********************************************/
Project::Project(const ProjectRootPath& rootPath)
: m_rootPath(rootPath)
, m_workRoot(*this, _SYS_STR(""))
, m_dotPath(m_workRoot, _SYS_STR(".hecl"))
, m_cookedRoot(m_dotPath, _SYS_STR("cooked"))
, m_specs(*this, _SYS_STR("specs"))
, m_paths(*this, _SYS_STR("paths"))
, m_groups(*this, _SYS_STR("groups")) {
/* Stat for existing project directory (must already exist) */
Sstat myStat;
if (hecl::Stat(m_rootPath.getAbsolutePath().data(), &myStat)) {
LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("unable to stat {}")), m_rootPath.getAbsolutePath());
return;
}
if (!S_ISDIR(myStat.st_mode)) {
LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("provided path must be a directory; '{}' isn't")),
m_rootPath.getAbsolutePath());
return;
}
/* Create project directory structure */
m_dotPath.makeDir();
m_cookedRoot.makeDir();
/* Ensure beacon is valid or created */
const ProjectPath beaconPath(m_dotPath, _SYS_STR("beacon"));
auto bf = hecl::FopenUnique(beaconPath.getAbsolutePath().data(), _SYS_STR("a+b"));
struct BeaconStruct {
hecl::FourCC magic;
uint32_t version;
} beacon;
constexpr uint32_t DATA_VERSION = 1;
if (std::fread(&beacon, 1, sizeof(beacon), bf.get()) != sizeof(beacon)) {
std::fseek(bf.get(), 0, SEEK_SET);
beacon.magic = HECLfcc;
beacon.version = SBig(DATA_VERSION);
std::fwrite(&beacon, 1, sizeof(beacon), bf.get());
}
bf.reset();
if (beacon.magic != HECLfcc || SBig(beacon.version) != DATA_VERSION) {
LogModule.report(logvisor::Fatal, FMT_STRING("incompatible project version"));
return;
}
/* Compile current dataspec */
rescanDataSpecs();
m_valid = true;
}
const ProjectPath& Project::getProjectCookedPath(const DataSpecEntry& spec) const {
for (const ProjectDataSpec& sp : m_compiledSpecs)
if (&sp.spec == &spec)
return sp.cookedPath;
LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Unable to find spec '{}'")), spec.m_name);
return m_cookedRoot;
}
bool Project::addPaths(const std::vector<ProjectPath>& paths) {
m_paths.lockAndRead();
for (const ProjectPath& path : paths)
m_paths.addLine(path.getRelativePathUTF8());
return m_paths.unlockAndCommit();
}
bool Project::removePaths(const std::vector<ProjectPath>& paths, bool recursive) {
std::vector<std::string>& existingPaths = m_paths.lockAndRead();
if (recursive) {
for (const ProjectPath& path : paths) {
auto recursiveBase = path.getRelativePathUTF8();
for (auto it = existingPaths.begin(); it != existingPaths.end();) {
if (!(*it).compare(0, recursiveBase.size(), recursiveBase)) {
it = existingPaths.erase(it);
continue;
}
++it;
}
}
} else
for (const ProjectPath& path : paths)
m_paths.removeLine(path.getRelativePathUTF8());
return m_paths.unlockAndCommit();
}
bool Project::addGroup(const hecl::ProjectPath& path) {
m_groups.lockAndRead();
m_groups.addLine(path.getRelativePathUTF8());
return m_groups.unlockAndCommit();
}
bool Project::removeGroup(const ProjectPath& path) {
m_groups.lockAndRead();
m_groups.removeLine(path.getRelativePathUTF8());
return m_groups.unlockAndCommit();
}
void Project::rescanDataSpecs() {
m_compiledSpecs.clear();
m_specs.lockAndRead();
for (const DataSpecEntry* spec : DATA_SPEC_REGISTRY) {
hecl::SystemString specStr(spec->m_name);
SystemUTF8Conv specUTF8(specStr);
m_compiledSpecs.push_back({*spec, ProjectPath(m_cookedRoot, hecl::SystemString(spec->m_name) + _SYS_STR(".spec")),
m_specs.checkForLine(specUTF8.str())});
}
m_specs.unlockAndDiscard();
}
bool Project::enableDataSpecs(const std::vector<SystemString>& specs) {
m_specs.lockAndRead();
for (const SystemString& spec : specs) {
SystemUTF8Conv specView(spec);
m_specs.addLine(specView.str());
}
bool result = m_specs.unlockAndCommit();
rescanDataSpecs();
return result;
}
bool Project::disableDataSpecs(const std::vector<SystemString>& specs) {
m_specs.lockAndRead();
for (const SystemString& spec : specs) {
SystemUTF8Conv specView(spec);
m_specs.removeLine(specView.str());
}
bool result = m_specs.unlockAndCommit();
rescanDataSpecs();
return result;
}
class CookProgress {
const hecl::MultiProgressPrinter& m_progPrinter;
const SystemChar* m_dir = nullptr;
const SystemChar* m_file = nullptr;
float m_prog = 0.f;
public:
CookProgress(const hecl::MultiProgressPrinter& progPrinter) : m_progPrinter(progPrinter) {}
void changeDir(const SystemChar* dir) {
m_dir = dir;
m_progPrinter.startNewLine();
}
void changeFile(const SystemChar* file, float prog) {
m_file = file;
m_prog = prog;
}
void reportFile(const DataSpecEntry* specEnt) {
SystemString submsg(m_file);
submsg += _SYS_STR(" (");
submsg += specEnt->m_name.data();
submsg += _SYS_STR(')');
m_progPrinter.print(m_dir, submsg.c_str(), m_prog);
}
void reportFile(const DataSpecEntry* specEnt, const SystemChar* extra) {
SystemString submsg(m_file);
submsg += _SYS_STR(" (");
submsg += specEnt->m_name.data();
submsg += _SYS_STR(", ");
submsg += extra;
submsg += _SYS_STR(')');
m_progPrinter.print(m_dir, submsg.c_str(), m_prog);
}
void reportDirComplete() { m_progPrinter.print(m_dir, nullptr, 1.f); }
};
static void VisitFile(const ProjectPath& path, bool force, bool fast,
std::vector<std::unique_ptr<IDataSpec>>& specInsts, CookProgress& progress, ClientProcess* cp) {
for (auto& spec : specInsts) {
if (spec->canCook(path, hecl::blender::SharedBlenderToken)) {
if (cp) {
cp->addCookTransaction(path, force, fast, spec.get());
} else {
const DataSpecEntry* override = spec->overrideDataSpec(path, spec->getDataSpecEntry());
if (!override)
continue;
ProjectPath cooked = path.getCookedPath(*override);
if (fast)
cooked = cooked.getWithExtension(_SYS_STR(".fast"));
if (force || cooked.getPathType() == ProjectPath::Type::None || path.getModtime() > cooked.getModtime()) {
progress.reportFile(override);
spec->doCook(path, cooked, fast, hecl::blender::SharedBlenderToken,
[&](const SystemChar* extra) { progress.reportFile(override, extra); });
}
}
}
}
}
static void VisitDirectory(const ProjectPath& dir, bool recursive, bool force, bool fast,
std::vector<std::unique_ptr<IDataSpec>>& specInsts, CookProgress& progress,
ClientProcess* cp) {
if (dir.getLastComponent().size() > 1 && dir.getLastComponent()[0] == _SYS_STR('.'))
return;
if (hecl::ProjectPath(dir, _SYS_STR("!project.yaml")).isFile() &&
hecl::ProjectPath(dir, _SYS_STR("!pool.yaml")).isFile()) {
/* Handle AudioGroup case */
VisitFile(dir, force, fast, specInsts, progress, cp);
return;
}
std::map<SystemString, ProjectPath> children;
dir.getDirChildren(children);
/* Pass 1: child file count */
int childFileCount = 0;
for (auto& child : children)
if (child.second.getPathType() == ProjectPath::Type::File)
++childFileCount;
/* Pass 2: child files */
int progNum = 0;
float progDenom = childFileCount;
progress.changeDir(dir.getLastComponent().data());
for (auto& child : children) {
if (child.second.getPathType() == ProjectPath::Type::File) {
progress.changeFile(child.first.c_str(), progNum++ / progDenom);
VisitFile(child.second, force, fast, specInsts, progress, cp);
}
}
progress.reportDirComplete();
/* Pass 3: child directories */
if (recursive) {
for (auto& child : children) {
switch (child.second.getPathType()) {
case ProjectPath::Type::Directory: {
VisitDirectory(child.second, recursive, force, fast, specInsts, progress, cp);
break;
}
default:
break;
}
}
}
}
bool Project::cookPath(const ProjectPath& path, const hecl::MultiProgressPrinter& progress, bool recursive, bool force,
bool fast, const DataSpecEntry* spec, ClientProcess* cp) {
/* Construct DataSpec instances for cooking */
if (spec) {
if (m_cookSpecs.size() != 1 || m_cookSpecs[0]->getDataSpecEntry() != spec) {
m_cookSpecs.clear();
if (spec->m_factory)
m_cookSpecs.push_back(spec->m_factory(*this, DataSpecTool::Cook));
}
} else if (m_cookSpecs.empty()) {
m_cookSpecs.reserve(m_compiledSpecs.size());
for (const ProjectDataSpec& projectSpec : m_compiledSpecs) {
if (projectSpec.active && projectSpec.spec.m_factory) {
m_cookSpecs.push_back(projectSpec.spec.m_factory(*this, DataSpecTool::Cook));
}
}
}
/* Iterate complete directory/file/glob list */
CookProgress cookProg(progress);
switch (path.getPathType()) {
case ProjectPath::Type::File:
case ProjectPath::Type::Glob: {
cookProg.changeFile(path.getLastComponent().data(), 0.f);
VisitFile(path, force, fast, m_cookSpecs, cookProg, cp);
break;
}
case ProjectPath::Type::Directory: {
VisitDirectory(path, recursive, force, fast, m_cookSpecs, cookProg, cp);
break;
}
default:
break;
}
return true;
}
bool Project::packagePath(const ProjectPath& path, const hecl::MultiProgressPrinter& progress, bool fast,
const DataSpecEntry* spec, ClientProcess* cp) {
/* Construct DataSpec instance for packaging */
const DataSpecEntry* specEntry = nullptr;
if (spec) {
if (spec->m_factory) {
specEntry = spec;
}
} else {
bool foundPC = false;
for (const ProjectDataSpec& projectSpec : m_compiledSpecs) {
if (projectSpec.active && projectSpec.spec.m_factory) {
if (hecl::StringUtils::EndsWith(projectSpec.spec.m_name, _SYS_STR("-PC"))) {
foundPC = true;
specEntry = &projectSpec.spec;
} else if (!foundPC) {
specEntry = &projectSpec.spec;
}
}
}
}
if (!specEntry)
LogModule.report(logvisor::Fatal, FMT_STRING("No matching DataSpec"));
if (!m_lastPackageSpec || m_lastPackageSpec->getDataSpecEntry() != specEntry)
m_lastPackageSpec = specEntry->m_factory(*this, DataSpecTool::Package);
if (m_lastPackageSpec->canPackage(path)) {
m_lastPackageSpec->doPackage(path, specEntry, fast, hecl::blender::SharedBlenderToken, progress, cp);
return true;
}
return false;
}
void Project::interruptCook() {
if (m_lastPackageSpec)
m_lastPackageSpec->interruptCook();
}
bool Project::cleanPath(const ProjectPath& path, bool recursive) { return false; }
PackageDepsgraph Project::buildPackageDepsgraph(const ProjectPath& path) { return PackageDepsgraph(); }
void Project::addBridgePathToCache(uint64_t id, const ProjectPath& path) { m_bridgePathCache[id] = path; }
void Project::clearBridgePathCache() { m_bridgePathCache.clear(); }
const ProjectPath* Project::lookupBridgePath(uint64_t id) const {
auto search = m_bridgePathCache.find(id);
if (search == m_bridgePathCache.cend())
return nullptr;
return &search->second;
}
} // namespace hecl::Database

377
hecl/lib/ProjectPath.cpp Normal file
View File

@@ -0,0 +1,377 @@
#include "hecl/hecl.hpp"
#include <regex>
#include "hecl/Database.hpp"
#include "hecl/FourCC.hpp"
namespace hecl {
static const SystemRegex regPATHCOMP(_SYS_STR("[/\\\\]*([^/\\\\]+)"), SystemRegex::ECMAScript | SystemRegex::optimize);
static SystemString CanonRelPath(SystemStringView path) {
/* Tokenize Path */
std::vector<SystemString> comps;
hecl::SystemRegexMatch matches;
SystemString in(path);
SanitizePath(in);
for (; std::regex_search(in, matches, regPATHCOMP); in = matches.suffix().str()) {
hecl::SystemRegexMatch::const_reference match = matches[1];
if (match == _SYS_STR("."))
continue;
else if (match == _SYS_STR("..")) {
if (comps.empty()) {
/* Unable to resolve outside project */
LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Unable to resolve outside project root in {}")), path);
return _SYS_STR(".");
}
comps.pop_back();
continue;
}
comps.push_back(match.str());
}
/* Emit relative path */
if (comps.size()) {
auto it = comps.begin();
SystemString retval = *it;
for (++it; it != comps.end(); ++it) {
if ((*it).size()) {
retval += _SYS_STR('/');
retval += *it;
}
}
return retval;
}
return _SYS_STR(".");
}
static SystemString CanonRelPath(SystemStringView path, const ProjectRootPath& projectRoot) {
/* Absolute paths not allowed; attempt to make project-relative */
if (IsAbsolute(path))
return CanonRelPath(projectRoot.getProjectRelativeFromAbsolute(path));
return CanonRelPath(path);
}
void ProjectPath::assign(Database::Project& project, SystemStringView path) {
m_proj = &project;
SystemString usePath;
size_t pipeFind = path.rfind(_SYS_STR('|'));
if (pipeFind != SystemString::npos) {
m_auxInfo.assign(path.cbegin() + pipeFind + 1, path.cend());
usePath.assign(path.cbegin(), path.cbegin() + pipeFind);
} else
usePath = path;
m_relPath = CanonRelPath(usePath, project.getProjectRootPath());
m_absPath = SystemString(project.getProjectRootPath().getAbsolutePath()) + _SYS_STR('/') + m_relPath;
SanitizePath(m_relPath);
SanitizePath(m_absPath);
ComputeHash();
}
#if HECL_UCS2
void ProjectPath::assign(Database::Project& project, std::string_view path) {
std::wstring wpath = UTF8ToWide(path);
assign(project, wpath);
}
#endif
void ProjectPath::assign(const ProjectPath& parentPath, SystemStringView path) {
m_proj = parentPath.m_proj;
SystemString usePath;
size_t pipeFind = path.rfind(_SYS_STR('|'));
if (pipeFind != SystemString::npos) {
m_auxInfo.assign(path.cbegin() + pipeFind + 1, path.cend());
usePath.assign(path.cbegin(), path.cbegin() + pipeFind);
} else
usePath = path;
m_relPath = CanonRelPath(parentPath.m_relPath + _SYS_STR('/') + usePath);
m_absPath = SystemString(m_proj->getProjectRootPath().getAbsolutePath()) + _SYS_STR('/') + m_relPath;
SanitizePath(m_relPath);
SanitizePath(m_absPath);
ComputeHash();
}
#if HECL_UCS2
void ProjectPath::assign(const ProjectPath& parentPath, std::string_view path) {
std::wstring wpath = UTF8ToWide(path);
assign(parentPath, wpath);
}
#endif
ProjectPath ProjectPath::getWithExtension(const SystemChar* ext, bool replace) const {
ProjectPath pp(*this);
if (replace) {
auto relIt = pp.m_relPath.end();
if (relIt != pp.m_relPath.begin())
--relIt;
auto absIt = pp.m_absPath.end();
if (absIt != pp.m_absPath.begin())
--absIt;
while (relIt != pp.m_relPath.begin() && *relIt != _SYS_STR('.') && *relIt != _SYS_STR('/')) {
--relIt;
--absIt;
}
if (*relIt == _SYS_STR('.') && relIt != pp.m_relPath.begin()) {
pp.m_relPath.resize(relIt - pp.m_relPath.begin());
pp.m_absPath.resize(absIt - pp.m_absPath.begin());
}
}
if (ext) {
pp.m_relPath += ext;
pp.m_absPath += ext;
}
pp.ComputeHash();
return pp;
}
ProjectPath ProjectPath::getCookedPath(const Database::DataSpecEntry& spec) const {
ProjectPath woExt = getWithExtension(nullptr, true);
ProjectPath ret(m_proj->getProjectCookedPath(spec), woExt.getRelativePath());
if (getAuxInfo().size())
return ret.getWithExtension((SystemString(_SYS_STR(".")) + getAuxInfo().data()).c_str());
else
return ret;
}
ProjectPath::Type ProjectPath::getPathType() const {
if (m_absPath.empty())
return Type::None;
if (m_absPath.find(_SYS_STR('*')) != SystemString::npos)
return Type::Glob;
Sstat theStat;
if (hecl::Stat(m_absPath.c_str(), &theStat))
return Type::None;
if (S_ISDIR(theStat.st_mode))
return Type::Directory;
if (S_ISREG(theStat.st_mode))
return Type::File;
return Type::None;
}
Time ProjectPath::getModtime() const {
Sstat theStat;
time_t latestTime = 0;
if (m_absPath.find(_SYS_STR('*')) != SystemString::npos) {
std::vector<ProjectPath> globResults;
getGlobResults(globResults);
for (ProjectPath& path : globResults) {
if (!hecl::Stat(path.getAbsolutePath().data(), &theStat)) {
if (S_ISREG(theStat.st_mode) && theStat.st_mtime > latestTime)
latestTime = theStat.st_mtime;
}
}
return Time(latestTime);
}
if (!hecl::Stat(m_absPath.c_str(), &theStat)) {
if (S_ISREG(theStat.st_mode)) {
return Time(theStat.st_mtime);
} else if (S_ISDIR(theStat.st_mode)) {
hecl::DirectoryEnumerator de(m_absPath, hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
for (const hecl::DirectoryEnumerator::Entry& ent : de) {
if (!hecl::Stat(ent.m_path.c_str(), &theStat)) {
if (S_ISREG(theStat.st_mode) && theStat.st_mtime > latestTime)
latestTime = theStat.st_mtime;
}
}
return Time(latestTime);
}
}
LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("invalid path type for computing modtime in '{}'")), m_absPath);
return Time();
}
static void _recursiveGlob(Database::Project& proj, std::vector<ProjectPath>& outPaths, const SystemString& remPath,
const SystemString& itStr, bool needSlash) {
SystemRegexMatch matches;
if (!std::regex_search(remPath, matches, regPATHCOMP))
return;
const SystemString& comp = matches[1];
if (comp.find(_SYS_STR('*')) == SystemString::npos) {
SystemString nextItStr = itStr;
if (needSlash)
nextItStr += _SYS_STR('/');
nextItStr += comp;
hecl::Sstat theStat;
if (Stat(nextItStr.c_str(), &theStat))
return;
if (S_ISDIR(theStat.st_mode))
_recursiveGlob(proj, outPaths, matches.suffix().str(), nextItStr, true);
else
outPaths.emplace_back(proj, nextItStr);
return;
}
/* Compile component into regex */
SystemRegex regComp(comp, SystemRegex::ECMAScript);
hecl::DirectoryEnumerator de(itStr, hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
for (const hecl::DirectoryEnumerator::Entry& ent : de) {
if (std::regex_match(ent.m_name, regComp)) {
SystemString nextItStr = itStr;
if (needSlash)
nextItStr += '/';
nextItStr += ent.m_name;
hecl::Sstat theStat;
if (Stat(nextItStr.c_str(), &theStat))
continue;
if (ent.m_isDir)
_recursiveGlob(proj, outPaths, matches.suffix().str(), nextItStr, true);
else
outPaths.emplace_back(proj, nextItStr);
}
}
}
void ProjectPath::getDirChildren(std::map<SystemString, ProjectPath>& outPaths) const {
hecl::DirectoryEnumerator de(m_absPath, hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
for (const hecl::DirectoryEnumerator::Entry& ent : de)
outPaths[ent.m_name] = ProjectPath(*this, ent.m_name);
}
hecl::DirectoryEnumerator ProjectPath::enumerateDir() const {
return hecl::DirectoryEnumerator(m_absPath, hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true);
}
void ProjectPath::getGlobResults(std::vector<ProjectPath>& outPaths) const {
auto rootPath = m_proj->getProjectRootPath().getAbsolutePath();
_recursiveGlob(*m_proj, outPaths, m_relPath, rootPath.data(), rootPath.back() != _SYS_STR('/'));
}
template <typename T>
static bool RegexSearchLast(const T& str, std::match_results<typename T::const_iterator>& m,
const std::basic_regex<typename T::value_type>& reg) {
using Iterator = std::regex_iterator<typename T::const_iterator>;
Iterator begin = Iterator(str.begin(), str.end(), reg);
Iterator end = Iterator();
if (begin == end)
return false;
Iterator last_it;
for (auto it = begin; it != end; ++it)
last_it = it;
m = *last_it;
return true;
}
static const hecl::SystemRegex regParsedHash32(_SYS_STR(R"(_([0-9a-fA-F]{8}))"),
std::regex::ECMAScript | std::regex::optimize);
uint32_t ProjectPath::parsedHash32() const {
if (!m_auxInfo.empty()) {
hecl::SystemRegexMatch match;
if (RegexSearchLast(m_auxInfo, match, regParsedHash32)) {
auto hexStr = match[1].str();
if (auto val = hecl::StrToUl(hexStr.c_str(), nullptr, 16))
return val;
}
} else {
hecl::SystemViewRegexMatch match;
if (RegexSearchLast(getLastComponent(), match, regParsedHash32)) {
auto hexStr = match[1].str();
if (auto val = hecl::StrToUl(hexStr.c_str(), nullptr, 16))
return val;
}
}
return hash().val32();
}
ProjectRootPath SearchForProject(SystemStringView path) {
ProjectRootPath testRoot(path);
auto begin = testRoot.getAbsolutePath().begin();
auto end = testRoot.getAbsolutePath().end();
while (begin != end) {
SystemString testPath(begin, end);
SystemString testIndexPath = testPath + _SYS_STR("/.hecl/beacon");
Sstat theStat;
if (!hecl::Stat(testIndexPath.c_str(), &theStat)) {
if (S_ISREG(theStat.st_mode)) {
const auto fp = hecl::FopenUnique(testIndexPath.c_str(), _SYS_STR("rb"));
if (fp == nullptr) {
continue;
}
char magic[4];
const size_t readSize = std::fread(magic, 1, sizeof(magic), fp.get());
if (readSize != sizeof(magic)) {
continue;
}
static constexpr hecl::FourCC hecl("HECL");
if (hecl::FourCC(magic) != hecl) {
continue;
}
return ProjectRootPath(testPath);
}
}
while (begin != end && *(end - 1) != _SYS_STR('/') && *(end - 1) != _SYS_STR('\\'))
--end;
if (begin != end)
--end;
}
return ProjectRootPath();
}
ProjectRootPath SearchForProject(SystemStringView path, SystemString& subpathOut) {
const ProjectRootPath testRoot(path);
auto begin = testRoot.getAbsolutePath().begin();
auto end = testRoot.getAbsolutePath().end();
while (begin != end) {
SystemString testPath(begin, end);
SystemString testIndexPath = testPath + _SYS_STR("/.hecl/beacon");
Sstat theStat;
if (!hecl::Stat(testIndexPath.c_str(), &theStat)) {
if (S_ISREG(theStat.st_mode)) {
const auto fp = hecl::FopenUnique(testIndexPath.c_str(), _SYS_STR("rb"));
if (fp == nullptr) {
continue;
}
char magic[4];
const size_t readSize = std::fread(magic, 1, sizeof(magic), fp.get());
if (readSize != sizeof(magic)) {
continue;
}
if (hecl::FourCC(magic) != FOURCC('HECL')) {
continue;
}
const ProjectRootPath newRootPath = ProjectRootPath(testPath);
const auto origEnd = testRoot.getAbsolutePath().end();
while (end != origEnd && *end != _SYS_STR('/') && *end != _SYS_STR('\\')) {
++end;
}
if (end != origEnd && (*end == _SYS_STR('/') || *end == _SYS_STR('\\'))) {
++end;
}
subpathOut.assign(end, origEnd);
return newRootPath;
}
}
while (begin != end && *(end - 1) != _SYS_STR('/') && *(end - 1) != _SYS_STR('\\')) {
--end;
}
if (begin != end) {
--end;
}
}
return ProjectRootPath();
}
} // namespace hecl

View File

@@ -0,0 +1,5 @@
set(RUNTIME_SOURCES
FileStoreManager.cpp
HMDL_RT.cpp)
hecl_add_list(Runtime RUNTIME_SOURCES)

View File

@@ -0,0 +1,60 @@
#include "hecl/Runtime.hpp"
#include "hecl/hecl.hpp"
#include <logvisor/logvisor.hpp>
#if _WIN32
#include <ShlObj.h>
#endif
#if WINDOWS_STORE
using namespace Windows::Storage;
#endif
namespace hecl::Runtime {
static logvisor::Module Log("FileStoreManager");
FileStoreManager::FileStoreManager(SystemStringView domain) : m_domain(domain) {
#if _WIN32
#if !WINDOWS_STORE
WCHAR home[MAX_PATH];
if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, home)))
Log.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to locate profile for file store")));
SystemString path(home);
#else
StorageFolder ^ cacheFolder = ApplicationData::Current->LocalCacheFolder;
SystemString path(cacheFolder->Path->Data());
#endif
path += _SYS_STR("/.heclrun");
hecl::MakeDir(path.c_str());
path += _SYS_STR('/');
path += domain.data();
hecl::MakeDir(path.c_str());
m_storeRoot = path;
#else
const char* xdg_data_home = getenv("XDG_DATA_HOME");
std::string path;
if (xdg_data_home) {
if (xdg_data_home[0] != '/')
Log.report(logvisor::Fatal, FMT_STRING("invalid $XDG_DATA_HOME for file store (must be absolute)"));
path = xdg_data_home;
} else {
const char* home = getenv("HOME");
if (!home)
Log.report(logvisor::Fatal, FMT_STRING("unable to locate $HOME for file store"));
path = home;
path += "/.local/share";
}
path += "/hecl/";
path += domain.data();
if (RecursiveMakeDir(path.c_str()) != 0)
Log.report(logvisor::Fatal, FMT_STRING("unable to mkdir at {}"), path);
m_storeRoot = path;
#endif
}
} // namespace hecl::Runtime

View File

@@ -0,0 +1,48 @@
#include "hecl/HMDLMeta.hpp"
#include "hecl/Runtime.hpp"
#include <athena/MemoryReader.hpp>
#include <logvisor/logvisor.hpp>
namespace hecl::Runtime {
static logvisor::Module HMDL_Log("HMDL");
HMDLData::HMDLData(boo::IGraphicsDataFactory::Context& ctx, const void* metaData, const void* vbo, const void* ibo) {
HMDLMeta meta;
{
athena::io::MemoryReader r(metaData, HECL_HMDL_META_SZ);
meta.read(r);
}
if (meta.magic != 'TACO')
HMDL_Log.report(logvisor::Fatal, FMT_STRING("invalid HMDL magic"));
m_vbo = ctx.newStaticBuffer(boo::BufferUse::Vertex, vbo, meta.vertStride, meta.vertCount);
m_ibo = ctx.newStaticBuffer(boo::BufferUse::Index, ibo, 4, meta.indexCount);
const size_t elemCount = 2 + meta.colorCount + meta.uvCount + meta.weightCount;
m_vtxFmtData = std::make_unique<boo::VertexElementDescriptor[]>(elemCount);
m_vtxFmtData[0].semantic = boo::VertexSemantic::Position3;
m_vtxFmtData[1].semantic = boo::VertexSemantic::Normal3;
size_t e = 2;
for (size_t i = 0; i < meta.colorCount; ++i, ++e) {
m_vtxFmtData[e].semantic = boo::VertexSemantic::ColorUNorm;
m_vtxFmtData[e].semanticIdx = i;
}
for (size_t i = 0; i < meta.uvCount; ++i, ++e) {
m_vtxFmtData[e].semantic = boo::VertexSemantic::UV2;
m_vtxFmtData[e].semanticIdx = i;
}
for (size_t i = 0; i < meta.weightCount; ++i, ++e) {
m_vtxFmtData[e].semantic = boo::VertexSemantic::Weight;
m_vtxFmtData[e].semanticIdx = i;
}
m_vtxFmt = boo::VertexFormatInfo(elemCount, m_vtxFmtData.get());
}
} // namespace hecl::Runtime

105
hecl/lib/SteamFinder.cpp Normal file
View File

@@ -0,0 +1,105 @@
#include "hecl/SteamFinder.hpp"
#include "hecl/hecl.hpp"
#ifdef WIN32
#include <winreg.h>
#define PATH_SEP L'\\'
#else
#define PATH_SEP '/'
#endif
namespace hecl {
/* Used to extract alternate steam install directories from libraryfolders.vdf */
static const std::regex regSteamPath(R"(^\s+\"[0-9]+\"\s+\"(.*)\")", std::regex::ECMAScript | std::regex::optimize);
hecl::SystemString FindCommonSteamApp(const hecl::SystemChar* name) {
hecl::SystemString steamInstallDir;
hecl::Sstat theStat;
#ifdef WIN32
#if !WINDOWS_STORE
HKEY hkey;
hecl::SystemChar _steamInstallDir[MAX_PATH] = {0};
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _SYS_STR("Software\\Valve\\Steam"), 0, KEY_QUERY_VALUE, &hkey) !=
ERROR_SUCCESS) {
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _SYS_STR("Software\\Valve\\Steam"), 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY,
&hkey) != ERROR_SUCCESS)
return {};
}
DWORD size = MAX_PATH;
if (RegQueryValueEx(hkey, _SYS_STR("InstallPath"), nullptr, nullptr, (LPBYTE)_steamInstallDir, &size) ==
ERROR_SUCCESS)
steamInstallDir = _steamInstallDir;
RegCloseKey(hkey);
if (steamInstallDir.empty())
return {};
#else
return {};
#endif
#elif defined(__APPLE__)
steamInstallDir = getenv("HOME");
steamInstallDir += "/Library/Application Support/Steam";
if (hecl::Stat(steamInstallDir.c_str(), &theStat) || !S_ISDIR(theStat.st_mode))
return {};
#else
steamInstallDir = getenv("HOME");
steamInstallDir += "/.local/share/Steam";
if (hecl::Stat(steamInstallDir.c_str(), &theStat) || !S_ISDIR(theStat.st_mode)) {
steamInstallDir = getenv("HOME");
steamInstallDir += "/.steam/steam";
if (hecl::Stat(steamInstallDir.c_str(), &theStat) || !S_ISDIR(theStat.st_mode))
return {};
}
#endif
const hecl::SystemString appPath = hecl::SystemString(_SYS_STR("common")) + PATH_SEP + name;
/* Try main steam install directory first */
const hecl::SystemString steamAppsMain = steamInstallDir + PATH_SEP + _SYS_STR("steamapps");
const hecl::SystemString mainAppPath = steamAppsMain + PATH_SEP + appPath;
if (!hecl::Stat(mainAppPath.c_str(), &theStat) && S_ISDIR(theStat.st_mode)) {
return mainAppPath;
}
/* Iterate alternate steam install dirs */
const hecl::SystemString libraryFoldersVdfPath = steamAppsMain + PATH_SEP + _SYS_STR("libraryfolders.vdf");
auto fp = hecl::FopenUnique(libraryFoldersVdfPath.c_str(), _SYS_STR("r"));
if (fp == nullptr) {
return {};
}
hecl::FSeek(fp.get(), 0, SEEK_END);
const int64_t fileLen = hecl::FTell(fp.get());
if (fileLen <= 0) {
return {};
}
hecl::FSeek(fp.get(), 0, SEEK_SET);
std::string fileBuf(fileLen, '\0');
if (std::fread(fileBuf.data(), 1, fileLen, fp.get()) != fileLen) {
return {};
}
std::smatch dirMatch;
auto begin = fileBuf.cbegin();
const auto end = fileBuf.cend();
while (std::regex_search(begin, end, dirMatch, regSteamPath)) {
const std::string match = dirMatch[1].str();
const hecl::SystemStringConv otherInstallDir(match);
const auto otherAppPath =
hecl::SystemString(otherInstallDir.sys_str()) + PATH_SEP + _SYS_STR("steamapps") + PATH_SEP + appPath;
if (!hecl::Stat(otherAppPath.c_str(), &theStat) && S_ISDIR(theStat.st_mode)) {
return otherAppPath;
}
begin = dirMatch.suffix().first;
}
return {};
}
} // namespace hecl

View File

@@ -0,0 +1,71 @@
#include <logvisor/logvisor.hpp>
#include <utf8proc.h>
namespace hecl {
static logvisor::Module Log("hecl-wsconv");
std::string WideToUTF8(std::wstring_view src) {
std::string retval;
retval.reserve(src.length());
for (wchar_t ch : src) {
utf8proc_uint8_t mb[4];
utf8proc_ssize_t c = utf8proc_encode_char(utf8proc_int32_t(ch), mb);
if (c < 0) {
Log.report(logvisor::Warning, FMT_STRING("invalid UTF-8 character while encoding"));
return retval;
}
retval.append(reinterpret_cast<char*>(mb), c);
}
return retval;
}
std::string Char16ToUTF8(std::u16string_view src) {
std::string retval;
retval.reserve(src.length());
for (char16_t ch : src) {
utf8proc_uint8_t mb[4];
utf8proc_ssize_t c = utf8proc_encode_char(utf8proc_int32_t(ch), mb);
if (c < 0) {
Log.report(logvisor::Warning, FMT_STRING("invalid UTF-8 character while encoding"));
return retval;
}
retval.append(reinterpret_cast<char*>(mb), c);
}
return retval;
}
std::wstring UTF8ToWide(std::string_view src) {
std::wstring retval;
retval.reserve(src.length());
const utf8proc_uint8_t* buf = reinterpret_cast<const utf8proc_uint8_t*>(src.data());
while (*buf) {
utf8proc_int32_t wc;
utf8proc_ssize_t len = utf8proc_iterate(buf, -1, &wc);
if (len < 0) {
Log.report(logvisor::Warning, FMT_STRING("invalid UTF-8 character while decoding"));
return retval;
}
buf += len;
retval += wchar_t(wc);
}
return retval;
}
std::u16string UTF8ToChar16(std::string_view src) {
std::u16string retval;
retval.reserve(src.length());
const utf8proc_uint8_t* buf = reinterpret_cast<const utf8proc_uint8_t*>(src.data());
while (*buf) {
utf8proc_int32_t wc;
utf8proc_ssize_t len = utf8proc_iterate(buf, -1, &wc);
if (len < 0) {
Log.report(logvisor::Warning, FMT_STRING("invalid UTF-8 character while decoding"));
return retval;
}
buf += len;
retval += char16_t(wc);
}
return retval;
}
} // namespace hecl

139
hecl/lib/closefrom.c Normal file
View File

@@ -0,0 +1,139 @@
/*
* Unix SMB/CIFS implementation.
* Samba utility functions
* Copyright (C) Volker Lendecke 2016
*
* ** NOTE! The following LGPL license applies to the replace
* ** library. This does NOT imply that all of Samba is released
* ** under the LGPL
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
static int closefrom_sysconf(int lower)
{
long max_files, fd;
max_files = sysconf(_SC_OPEN_MAX);
if (max_files == -1) {
max_files = 65536;
}
for (fd=lower; fd<max_files; fd++) {
close(fd);
}
return 0;
}
static int closefrom_procfs(int lower)
{
DIR *dirp;
int dir_fd;
struct dirent *dp;
int *fds = NULL;
size_t num_fds = 0;
size_t fd_array_size = 0;
size_t i;
int ret = ENOMEM;
dirp = opendir("/proc/self/fd");
if (dirp == 0) {
return errno;
}
dir_fd = dirfd(dirp);
if (dir_fd == -1) {
ret = errno;
goto fail;
}
while ((dp = readdir(dirp)) != NULL) {
char *endptr;
unsigned long long fd;
errno = 0;
fd = strtoull(dp->d_name, &endptr, 10);
if ((fd == 0) && (errno == EINVAL)) {
continue;
}
if ((fd == ULLONG_MAX) && (errno == ERANGE)) {
continue;
}
if (*endptr != '\0') {
continue;
}
if (fd == dir_fd) {
continue;
}
if (fd > INT_MAX) {
continue;
}
if (fd < lower) {
continue;
}
if (num_fds >= (fd_array_size / sizeof(int))) {
void *tmp;
if (fd_array_size == 0) {
fd_array_size = 16 * sizeof(int);
} else {
if (fd_array_size + fd_array_size <
fd_array_size) {
/* overflow */
goto fail;
}
fd_array_size = fd_array_size + fd_array_size;
}
tmp = realloc(fds, fd_array_size);
if (tmp == NULL) {
goto fail;
}
fds = tmp;
}
fds[num_fds++] = fd;
}
for (i=0; i<num_fds; i++) {
close(fds[i]);
}
ret = 0;
fail:
closedir(dirp);
free(fds);
return ret;
}
int rep_closefrom(int lower)
{
int ret;
ret = closefrom_procfs(lower);
if (ret == 0) {
return 0;
}
return closefrom_sysconf(lower);
}

834
hecl/lib/hecl.cpp Normal file
View File

@@ -0,0 +1,834 @@
#include "hecl/hecl.hpp"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#ifdef WIN32
#include <windows.h>
#ifndef _WIN32_IE
#define _WIN32_IE 0x0400
#endif
#include <shlobj.h>
#endif
#ifdef __APPLE__
#include <Carbon/Carbon.h>
#endif
#ifdef __linux__
#include <mntent.h>
#include <sys/wait.h>
#endif
#include <logvisor/logvisor.hpp>
using namespace std::literals;
namespace hecl {
unsigned VerbosityLevel = 0;
bool GuiMode = false;
logvisor::Module LogModule("hecl");
constexpr std::string_view Illegals = "<>?\""sv;
void SanitizePath(std::string& path) {
if (path.empty())
return;
path.erase(std::remove(path.begin(), path.end(), '\n'), path.end());
path.erase(std::remove(path.begin(), path.end(), '\r'), path.end());
std::string::iterator p1 = path.begin();
bool ic = false;
std::transform(path.begin(), path.end(), path.begin(), [&](const char a) -> char {
++p1;
if (Illegals.find_first_of(a) != std::string::npos) {
ic = false;
return '_';
}
if (ic) {
ic = false;
return a;
}
if (a == '\\' && (p1 == path.end() || *p1 != '\\')) {
ic = true;
return '/';
}
return a;
});
while (path.back() == '/')
path.pop_back();
}
constexpr std::wstring_view WIllegals = L"<>?\""sv;
void SanitizePath(std::wstring& path) {
if (path.empty())
return;
path.erase(std::remove(path.begin(), path.end(), L'\n'), path.end());
path.erase(std::remove(path.begin(), path.end(), L'\r'), path.end());
std::wstring::iterator p1 = path.begin();
bool ic = false;
std::transform(path.begin(), path.end(), path.begin(), [&](const wchar_t a) -> wchar_t {
++p1;
if (WIllegals.find_first_of(a) != std::wstring::npos) {
ic = false;
return L'_';
}
if (ic) {
ic = false;
return a;
}
if (a == L'\\' && (p1 == path.end() || *p1 != L'\\')) {
ic = true;
return L'/';
}
return a;
});
while (path.back() == L'/')
path.pop_back();
}
SystemString GetcwdStr() {
/* http://stackoverflow.com/a/2869667 */
// const size_t ChunkSize=255;
// const int MaxChunks=10240; // 2550 KiBs of current path are more than enough
SystemChar stackBuffer[255]; // Stack buffer for the "normal" case
if (Getcwd(stackBuffer, int(std::size(stackBuffer))) != nullptr) {
return SystemString(stackBuffer);
}
if (errno != ERANGE) {
// It's not ERANGE, so we don't know how to handle it
LogModule.report(logvisor::Fatal, FMT_STRING("Cannot determine the current path."));
// Of course you may choose a different error reporting method
}
// Ok, the stack buffer isn't long enough; fallback to heap allocation
for (int chunks = 2; chunks < 10240; chunks++) {
// With boost use scoped_ptr; in C++0x, use unique_ptr
// If you want to be less C++ but more efficient you may want to use realloc
const int bufSize = 255 * chunks;
std::unique_ptr<SystemChar[]> cwd(new SystemChar[bufSize]);
if (Getcwd(cwd.get(), bufSize) != nullptr) {
return SystemString(cwd.get());
}
if (errno != ERANGE) {
// It's not ERANGE, so we don't know how to handle it
LogModule.report(logvisor::Fatal, FMT_STRING("Cannot determine the current path."));
// Of course you may choose a different error reporting method
}
}
LogModule.report(logvisor::Fatal, FMT_STRING("Cannot determine the current path; the path is apparently unreasonably long"));
return SystemString();
}
static std::mutex PathsMutex;
static std::unordered_map<std::thread::id, ProjectPath> PathsInProgress;
bool ResourceLock::InProgress(const ProjectPath& path) {
std::unique_lock lk{PathsMutex};
return std::any_of(PathsInProgress.cbegin(), PathsInProgress.cend(),
[&path](const auto& entry) { return entry.second == path; });
}
bool ResourceLock::SetThreadRes(const ProjectPath& path) {
std::unique_lock lk{PathsMutex};
if (PathsInProgress.find(std::this_thread::get_id()) != PathsInProgress.cend()) {
LogModule.report(logvisor::Fatal, FMT_STRING("multiple resource locks on thread"));
}
const bool isInProgress = std::any_of(PathsInProgress.cbegin(), PathsInProgress.cend(),
[&path](const auto& entry) { return entry.second == path; });
if (isInProgress) {
return false;
}
PathsInProgress.insert_or_assign(std::this_thread::get_id(), path);
return true;
}
void ResourceLock::ClearThreadRes() {
std::unique_lock lk{PathsMutex};
PathsInProgress.erase(std::this_thread::get_id());
}
bool IsPathPNG(const hecl::ProjectPath& path) {
const auto fp = hecl::FopenUnique(path.getAbsolutePath().data(), _SYS_STR("rb"));
if (fp == nullptr) {
return false;
}
uint32_t buf = 0;
if (std::fread(&buf, 1, sizeof(buf), fp.get()) != sizeof(buf)) {
return false;
}
buf = hecl::SBig(buf);
return buf == 0x89504e47;
}
bool IsPathBlend(const hecl::ProjectPath& path) {
const auto lastCompExt = path.getLastComponentExt();
if (lastCompExt.empty() || lastCompExt != _SYS_STR("blend"))
return false;
const auto fp = hecl::FopenUnique(path.getAbsolutePath().data(), _SYS_STR("rb"));
if (fp == nullptr) {
return false;
}
uint32_t buf = 0;
if (std::fread(&buf, 1, sizeof(buf), fp.get()) != sizeof(buf)) {
return false;
}
buf = hecl::SLittle(buf);
return buf == 0x4e454c42 || buf == 0x88b1f;
}
bool IsPathYAML(const hecl::ProjectPath& path) {
auto lastComp = path.getLastComponent();
if (lastComp == _SYS_STR("!catalog.yaml") ||
lastComp == _SYS_STR("!memoryid.yaml") ||
lastComp == _SYS_STR("!memoryrelays.yaml"))
return false; /* !catalog.yaml, !memoryid.yaml, !memoryrelays.yaml are exempt from general use */
auto lastCompExt = path.getLastComponentExt();
if (lastCompExt.empty())
return false;
return lastCompExt == _SYS_STR("yaml") || lastCompExt == _SYS_STR("yml");
}
hecl::DirectoryEnumerator::DirectoryEnumerator(SystemStringView path, Mode mode, bool sizeSort, bool reverse,
bool noHidden) {
hecl::Sstat theStat;
if (hecl::Stat(path.data(), &theStat) || !S_ISDIR(theStat.st_mode))
return;
#if _WIN32
hecl::SystemString wc(path);
wc += _SYS_STR("/*");
WIN32_FIND_DATAW d;
HANDLE dir = FindFirstFileW(wc.c_str(), &d);
if (dir == INVALID_HANDLE_VALUE)
return;
switch (mode) {
case Mode::Native:
do {
if (!wcscmp(d.cFileName, _SYS_STR(".")) || !wcscmp(d.cFileName, _SYS_STR("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
hecl::SystemString fp(path);
fp += _SYS_STR('/');
fp += d.cFileName;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st))
continue;
size_t sz = 0;
bool isDir = false;
if (S_ISDIR(st.st_mode))
isDir = true;
else if (S_ISREG(st.st_mode))
sz = st.st_size;
else
continue;
m_entries.emplace_back(fp, d.cFileName, sz, isDir);
} while (FindNextFileW(dir, &d));
break;
case Mode::DirsThenFilesSorted:
case Mode::DirsSorted: {
std::map<hecl::SystemString, Entry, CaseInsensitiveCompare> sort;
do {
if (!wcscmp(d.cFileName, _SYS_STR(".")) || !wcscmp(d.cFileName, _SYS_STR("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
hecl::SystemString fp(path);
fp += _SYS_STR('/');
fp += d.cFileName;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode))
continue;
sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, 0, true)));
} while (FindNextFileW(dir, &d));
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
if (mode == Mode::DirsSorted)
break;
FindClose(dir);
dir = FindFirstFileW(wc.c_str(), &d);
}
case Mode::FilesSorted: {
if (mode == Mode::FilesSorted)
m_entries.clear();
if (sizeSort) {
std::multimap<size_t, Entry> sort;
do {
if (!wcscmp(d.cFileName, _SYS_STR(".")) || !wcscmp(d.cFileName, _SYS_STR("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
hecl::SystemString fp(path);
fp += _SYS_STR('/');
fp += d.cFileName;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d.cFileName, st.st_size, false)));
} while (FindNextFileW(dir, &d));
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
} else {
std::map<hecl::SystemString, Entry, CaseInsensitiveCompare> sort;
do {
if (!wcscmp(d.cFileName, _SYS_STR(".")) || !wcscmp(d.cFileName, _SYS_STR("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
hecl::SystemString fp(path);
fp += _SYS_STR('/');
fp += d.cFileName;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, st.st_size, false)));
} while (FindNextFileW(dir, &d));
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
}
break;
}
}
FindClose(dir);
#else
DIR* dir = opendir(path.data());
if (!dir)
return;
const dirent* d;
switch (mode) {
case Mode::Native:
while ((d = readdir(dir))) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
hecl::SystemString fp(path);
fp += '/';
fp += d->d_name;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st))
continue;
size_t sz = 0;
bool isDir = false;
if (S_ISDIR(st.st_mode))
isDir = true;
else if (S_ISREG(st.st_mode))
sz = st.st_size;
else
continue;
m_entries.push_back(Entry(std::move(fp), d->d_name, sz, isDir));
}
break;
case Mode::DirsThenFilesSorted:
case Mode::DirsSorted: {
std::map<hecl::SystemString, Entry, CaseInsensitiveCompare> sort;
while ((d = readdir(dir))) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
hecl::SystemString fp(path);
fp += '/';
fp += d->d_name;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode))
continue;
sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, 0, true)));
}
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
if (mode == Mode::DirsSorted)
break;
rewinddir(dir);
[[fallthrough]];
}
case Mode::FilesSorted: {
if (mode == Mode::FilesSorted)
m_entries.clear();
if (sizeSort) {
std::multimap<size_t, Entry> sort;
while ((d = readdir(dir))) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
hecl::SystemString fp(path);
fp += '/';
fp += d->d_name;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d->d_name, st.st_size, false)));
}
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
} else {
std::map<hecl::SystemString, Entry, CaseInsensitiveCompare> sort;
while ((d = readdir(dir))) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
hecl::SystemString fp(path);
fp += '/';
fp += d->d_name;
hecl::Sstat st;
if (hecl::Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, st.st_size, false)));
}
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
}
break;
}
}
closedir(dir);
#endif
}
static std::pair<hecl::SystemString, std::string> NameFromPath(hecl::SystemStringView path) {
hecl::SystemUTF8Conv utf8(path);
if (utf8.str().size() == 1 && utf8.str()[0] == '/')
return {hecl::SystemString(path), "/"};
size_t lastSlash = utf8.str().rfind('/');
if (lastSlash != std::string::npos)
return {hecl::SystemString(path), std::string(utf8.str().cbegin() + lastSlash + 1, utf8.str().cend())};
else
return {hecl::SystemString(path), std::string(utf8.str())};
}
std::vector<std::pair<hecl::SystemString, std::string>> GetSystemLocations() {
std::vector<std::pair<hecl::SystemString, std::string>> ret;
#ifdef WIN32
#if !WINDOWS_STORE
/* Add the drive names to the listing (as queried by blender) */
{
constexpr uint32_t FILE_MAXDIR = 768;
wchar_t wline[FILE_MAXDIR];
const uint32_t tmp = GetLogicalDrives();
for (uint32_t i = 0; i < 26; i++) {
if ((tmp >> i) & 1) {
wline[0] = L'A' + i;
wline[1] = L':';
wline[2] = L'/';
wline[3] = L'\0';
wchar_t* name = nullptr;
/* Flee from horrible win querying hover floppy drives! */
if (i > 1) {
/* Try to get volume label as well... */
if (GetVolumeInformationW(wline, wline + 4, FILE_MAXDIR - 4, nullptr, nullptr, nullptr, nullptr, 0)) {
const size_t labelLen = std::wcslen(wline + 4);
_snwprintf(wline + 4 + labelLen, FILE_MAXDIR - 4 - labelLen, L" (%.2s)", wline);
name = wline + 4;
}
}
wline[2] = L'\0';
if (name == nullptr) {
ret.push_back(NameFromPath(wline));
} else {
ret.emplace_back(wline, hecl::WideToUTF8(name));
}
}
}
/* Adding Desktop and My Documents */
SystemString wpath;
SHGetSpecialFolderPathW(nullptr, wline, CSIDL_PERSONAL, 0);
wpath.assign(wline);
SanitizePath(wpath);
ret.push_back(NameFromPath(wpath));
SHGetSpecialFolderPathW(nullptr, wline, CSIDL_DESKTOPDIRECTORY, 0);
wpath.assign(wline);
SanitizePath(wpath);
ret.push_back(NameFromPath(wpath));
}
#endif
#else
#ifdef __APPLE__
{
hecl::Sstat theStat;
const char* home = getenv("HOME");
if (home) {
ret.push_back(NameFromPath(home));
std::string desktop(home);
desktop += "/Desktop";
if (!hecl::Stat(desktop.c_str(), &theStat))
ret.push_back(NameFromPath(desktop));
}
/* Get mounted volumes better method OSX 10.6 and higher, see: */
/*https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFURLRef/Reference/reference.html*/
/* we get all volumes sorted including network and do not relay on user-defined finder visibility, less confusing */
CFURLRef cfURL = nullptr;
CFURLEnumeratorResult result = kCFURLEnumeratorSuccess;
CFURLEnumeratorRef volEnum =
CFURLEnumeratorCreateForMountedVolumes(nullptr, kCFURLEnumeratorSkipInvisibles, nullptr);
while (result != kCFURLEnumeratorEnd) {
char defPath[1024];
result = CFURLEnumeratorGetNextURL(volEnum, &cfURL, nullptr);
if (result != kCFURLEnumeratorSuccess) {
continue;
}
CFURLGetFileSystemRepresentation(cfURL, false, reinterpret_cast<UInt8*>(defPath), std::size(defPath));
ret.push_back(NameFromPath(defPath));
}
CFRelease(volEnum);
}
#else
/* unix */
{
hecl::Sstat theStat;
const char* home = getenv("HOME");
if (home) {
ret.push_back(NameFromPath(home));
std::string desktop(home);
desktop += "/Desktop";
if (!hecl::Stat(desktop.c_str(), &theStat))
ret.push_back(NameFromPath(desktop));
}
{
bool found = false;
#ifdef __linux__
/* Loop over mount points */
struct mntent* mnt;
FILE* fp = setmntent(MOUNTED, "r");
if (fp) {
while ((mnt = getmntent(fp))) {
if (strlen(mnt->mnt_fsname) < 4 || strncmp(mnt->mnt_fsname, "/dev", 4))
continue;
std::string mntStr(mnt->mnt_dir);
if (mntStr.size() > 1 && mntStr.back() == '/')
mntStr.pop_back();
ret.push_back(NameFromPath(mntStr));
found = true;
}
endmntent(fp);
}
#endif
/* Fallback */
if (!found)
ret.push_back(NameFromPath("/"));
}
}
#endif
#endif
return ret;
}
std::wstring Char16ToWide(std::u16string_view src) { return std::wstring(src.begin(), src.end()); }
/* recursive mkdir */
#if _WIN32
int RecursiveMakeDir(const SystemChar* dir) {
SystemChar tmp[1024];
/* copy path */
std::wcsncpy(tmp, dir, std::size(tmp));
const size_t len = std::wcslen(tmp);
if (len >= std::size(tmp)) {
return -1;
}
/* remove trailing slash */
if (tmp[len - 1] == '/' || tmp[len - 1] == '\\') {
tmp[len - 1] = 0;
}
/* recursive mkdir */
SystemChar* p = nullptr;
Sstat sb;
for (p = tmp + 1; *p; p++) {
if (*p == '/' || *p == '\\') {
*p = 0;
/* test path */
if (Stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (!CreateDirectoryW(tmp, nullptr)) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
*p = '/';
}
}
/* test path */
if (Stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (!CreateDirectoryW(tmp, nullptr)) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
return 0;
}
#else
int RecursiveMakeDir(const SystemChar* dir) {
SystemChar tmp[1024];
/* copy path */
std::memset(tmp, 0, std::size(tmp));
std::strncpy(tmp, dir, std::size(tmp) - 1);
const size_t len = std::strlen(tmp);
if (len >= std::size(tmp)) {
return -1;
}
/* remove trailing slash */
if (tmp[len - 1] == '/') {
tmp[len - 1] = 0;
}
/* recursive mkdir */
SystemChar* p = nullptr;
Sstat sb;
for (p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = 0;
/* test path */
if (Stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (mkdir(tmp, 0755) < 0) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
*p = '/';
}
}
/* test path */
if (Stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (mkdir(tmp, 0755) < 0) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
return 0;
}
#endif
const SystemChar* GetTmpDir() {
#ifdef _WIN32
#if WINDOWS_STORE
const wchar_t* TMPDIR = nullptr;
#else
const wchar_t* TMPDIR = _wgetenv(L"TEMP");
if (!TMPDIR)
TMPDIR = L"\\Temp";
#endif
#else
const char* TMPDIR = getenv("TMPDIR");
if (!TMPDIR)
TMPDIR = "/tmp";
#endif
return TMPDIR;
}
#if !WINDOWS_STORE
int RunProcess(const SystemChar* path, const SystemChar* const args[]) {
#ifdef _WIN32
SECURITY_ATTRIBUTES sattrs = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
HANDLE consoleOutReadTmp = INVALID_HANDLE_VALUE;
HANDLE consoleOutWrite = INVALID_HANDLE_VALUE;
if (!CreatePipe(&consoleOutReadTmp, &consoleOutWrite, &sattrs, 0)) {
LogModule.report(logvisor::Fatal, FMT_STRING("Error with CreatePipe"));
return -1;
}
HANDLE consoleErrWrite = INVALID_HANDLE_VALUE;
if (!DuplicateHandle(GetCurrentProcess(), consoleOutWrite, GetCurrentProcess(), &consoleErrWrite, 0, TRUE,
DUPLICATE_SAME_ACCESS)) {
LogModule.report(logvisor::Fatal, FMT_STRING("Error with DuplicateHandle"));
CloseHandle(consoleOutReadTmp);
CloseHandle(consoleOutWrite);
return -1;
}
HANDLE consoleOutRead = INVALID_HANDLE_VALUE;
if (!DuplicateHandle(GetCurrentProcess(), consoleOutReadTmp, GetCurrentProcess(),
&consoleOutRead, // Address of new handle.
0, FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS)) {
LogModule.report(logvisor::Fatal, FMT_STRING("Error with DuplicateHandle"));
CloseHandle(consoleOutReadTmp);
CloseHandle(consoleOutWrite);
CloseHandle(consoleErrWrite);
return -1;
}
CloseHandle(consoleOutReadTmp);
hecl::SystemString cmdLine;
const SystemChar* const* arg = &args[1];
while (*arg) {
cmdLine += _SYS_STR(" \"");
cmdLine += *arg++;
cmdLine += _SYS_STR('"');
}
STARTUPINFO sinfo = {sizeof(STARTUPINFO)};
HANDLE nulHandle = CreateFileW(L"nul", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sattrs, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
sinfo.dwFlags = STARTF_USESTDHANDLES;
sinfo.hStdInput = nulHandle;
sinfo.hStdError = consoleErrWrite;
sinfo.hStdOutput = consoleOutWrite;
PROCESS_INFORMATION pinfo = {};
if (!CreateProcessW(path, cmdLine.data(), nullptr, nullptr, TRUE, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &sinfo,
&pinfo)) {
LPWSTR messageBuffer = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr);
LogModule.report(logvisor::Error, FMT_STRING(L"unable to launch process from {}: {}"), path, messageBuffer);
LocalFree(messageBuffer);
CloseHandle(nulHandle);
CloseHandle(consoleErrWrite);
CloseHandle(consoleOutWrite);
CloseHandle(consoleOutRead);
return -1;
}
CloseHandle(nulHandle);
CloseHandle(consoleErrWrite);
CloseHandle(consoleOutWrite);
bool consoleThreadRunning = true;
auto consoleThread = std::thread([=, &consoleThreadRunning]() {
CHAR lpBuffer[256];
DWORD nBytesRead;
DWORD nCharsWritten;
while (consoleThreadRunning) {
if (!ReadFile(consoleOutRead, lpBuffer, sizeof(lpBuffer), &nBytesRead, nullptr) || !nBytesRead) {
DWORD err = GetLastError();
if (err == ERROR_BROKEN_PIPE)
break; // pipe done - normal exit path.
else
LogModule.report(logvisor::Error, FMT_STRING("Error with ReadFile: {:08X}"), err); // Something bad happened.
}
// Display the character read on the screen.
auto lk = logvisor::LockLog();
if (!WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, nBytesRead, &nCharsWritten, nullptr)) {
// LogModule.report(logvisor::Error, FMT_STRING("Error with WriteConsole: {:08X}"), GetLastError());
}
}
CloseHandle(consoleOutRead);
});
WaitForSingleObject(pinfo.hProcess, INFINITE);
DWORD ret;
if (!GetExitCodeProcess(pinfo.hProcess, &ret))
ret = -1;
consoleThreadRunning = false;
if (consoleThread.joinable())
consoleThread.join();
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
return ret;
#else
pid_t pid = fork();
if (!pid) {
closefrom(3);
execvp(path, (char* const*)args);
exit(1);
}
int ret;
if (waitpid(pid, &ret, 0) < 0)
return -1;
if (WIFEXITED(ret))
return WEXITSTATUS(ret);
return -1;
#endif
}
#endif
} // namespace hecl