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:
8
hecl/lib/Blender/CMakeLists.txt
Normal file
8
hecl/lib/Blender/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(BLENDER_SOURCES
|
||||
Connection.cpp
|
||||
MeshOptimizer.hpp
|
||||
MeshOptimizer.cpp
|
||||
SDNARead.cpp
|
||||
HMDL.cpp)
|
||||
|
||||
hecl_add_list(Blender BLENDER_SOURCES)
|
||||
1752
hecl/lib/Blender/Connection.cpp
Normal file
1752
hecl/lib/Blender/Connection.cpp
Normal file
File diff suppressed because it is too large
Load Diff
176
hecl/lib/Blender/HMDL.cpp
Normal file
176
hecl/lib/Blender/HMDL.cpp
Normal 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
|
||||
439
hecl/lib/Blender/MeshOptimizer.cpp
Normal file
439
hecl/lib/Blender/MeshOptimizer.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
117
hecl/lib/Blender/MeshOptimizer.hpp
Normal file
117
hecl/lib/Blender/MeshOptimizer.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
190
hecl/lib/Blender/SDNARead.cpp
Normal file
190
hecl/lib/Blender/SDNARead.cpp
Normal 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
104
hecl/lib/CMakeLists.txt
Normal 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
556
hecl/lib/CVar.cpp
Normal 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
78
hecl/lib/CVarCommons.cpp
Normal 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
366
hecl/lib/CVarManager.cpp
Normal 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
236
hecl/lib/ClientProcess.cpp
Normal 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
292
hecl/lib/Compilers.cpp
Normal 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
445
hecl/lib/Console.cpp
Normal 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
151
hecl/lib/HumanizeNumber.cpp
Normal 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
|
||||
365
hecl/lib/MultiProgressPrinter.cpp
Normal file
365
hecl/lib/MultiProgressPrinter.cpp
Normal 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
167
hecl/lib/Pipeline.cpp
Normal 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
505
hecl/lib/Project.cpp
Normal 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
377
hecl/lib/ProjectPath.cpp
Normal 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
|
||||
5
hecl/lib/Runtime/CMakeLists.txt
Normal file
5
hecl/lib/Runtime/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
set(RUNTIME_SOURCES
|
||||
FileStoreManager.cpp
|
||||
HMDL_RT.cpp)
|
||||
|
||||
hecl_add_list(Runtime RUNTIME_SOURCES)
|
||||
60
hecl/lib/Runtime/FileStoreManager.cpp
Normal file
60
hecl/lib/Runtime/FileStoreManager.cpp
Normal 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
|
||||
48
hecl/lib/Runtime/HMDL_RT.cpp
Normal file
48
hecl/lib/Runtime/HMDL_RT.cpp
Normal 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
105
hecl/lib/SteamFinder.cpp
Normal 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
|
||||
71
hecl/lib/WideStringConvert.cpp
Normal file
71
hecl/lib/WideStringConvert.cpp
Normal 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
139
hecl/lib/closefrom.c
Normal 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
834
hecl/lib/hecl.cpp
Normal 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
|
||||
Reference in New Issue
Block a user