Fixes
This commit is contained in:
@ -36,6 +36,13 @@ namespace ia::iae
|
||||
glm::orthoLH(0.0f, (FLOAT32) width, (FLOAT32) height, 0.0f, Renderer::MIN_DEPTH, Renderer::MAX_DEPTH);
|
||||
}
|
||||
|
||||
CONST Mat4 *CameraComponent::GetViewMatrix()
|
||||
{
|
||||
const auto pos = m_node->GetPosition() + m_positionOffset;
|
||||
m_viewMatrix = glm::lookAtLH(glm::vec3{pos, -2.0f}, {pos, 0.0f}, {0.0f, 1.0f, 0.0f});
|
||||
return &m_viewMatrix;
|
||||
}
|
||||
|
||||
VOID CameraComponent::Draw()
|
||||
{
|
||||
}
|
||||
@ -46,8 +53,6 @@ namespace ia::iae
|
||||
|
||||
VOID CameraComponent::Update()
|
||||
{
|
||||
const auto pos = m_node->GetPosition() + m_positionOffset;
|
||||
m_viewMatrix = glm::lookAtLH(glm::vec3{pos, -2.0f}, {pos, 0.0f}, {0.0f, 1.0f, 0.0f});
|
||||
}
|
||||
|
||||
VOID CameraComponent::FixedUpdate()
|
||||
|
||||
@ -38,10 +38,25 @@ namespace ia::iae
|
||||
#endif
|
||||
}
|
||||
|
||||
String Engine::ReadTextAsset(IN CONST String &path)
|
||||
{
|
||||
SDL_IOStream *f = SDL_IOFromFile(path.c_str(), "r");
|
||||
if (!f)
|
||||
THROW_FILE_OPEN_READ(path);
|
||||
Vector<CHAR> result;
|
||||
SDL_SeekIO(f, 0, SDL_IO_SEEK_END);
|
||||
result.resize(SDL_TellIO(f) + 1);
|
||||
SDL_SeekIO(f, 0, SDL_IO_SEEK_SET);
|
||||
SDL_ReadIO(f, result.data(), result.size());
|
||||
result.back() = '\0';
|
||||
SDL_CloseIO(f);
|
||||
return result.data();
|
||||
}
|
||||
|
||||
Vector<UINT8> Engine::ReadBinaryAsset(IN CONST String &path)
|
||||
{
|
||||
SDL_IOStream* f = SDL_IOFromFile(path.c_str(), "rb");
|
||||
if(!f)
|
||||
SDL_IOStream *f = SDL_IOFromFile(path.c_str(), "rb");
|
||||
if (!f)
|
||||
THROW_FILE_OPEN_READ(path);
|
||||
Vector<UINT8> result;
|
||||
SDL_SeekIO(f, 0, SDL_IO_SEEK_END);
|
||||
@ -90,12 +105,6 @@ namespace ia::iae
|
||||
return CreateSound(name, data.data(), data.size());
|
||||
}
|
||||
|
||||
RefPtr<Scene> Engine::CreateSceneFromFile(IN CONST String &path)
|
||||
{
|
||||
const auto data = File::ReadToString(path.c_str());
|
||||
return Scene::Create(data);
|
||||
}
|
||||
|
||||
Handle Engine::ResizeImage(IN CONST String &name, IN INT32 newWidth, IN INT32 newHeight)
|
||||
{
|
||||
return ResizeImage(GetImage(name), newWidth, newHeight);
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
STATIC Mat4 IdentityMatrix(1.0f);
|
||||
|
||||
EXTERN SDL_Window *g_windowHandle;
|
||||
|
||||
INT32 Renderer::s_screenWidth{};
|
||||
@ -136,8 +138,6 @@ namespace ia::iae
|
||||
|
||||
VOID Renderer::BeginFrame()
|
||||
{
|
||||
STATIC Mat4 IdentityMatrix(1.0f);
|
||||
|
||||
if (!(s_state.ActiveCommandBuffer = SDL_AcquireGPUCommandBuffer(s_gpuDevice)))
|
||||
THROW_UNKNOWN("Failed to acquire SDL GPU command buffer: ", SDL_GetError());
|
||||
|
||||
@ -152,10 +152,6 @@ namespace ia::iae
|
||||
SDL_PushGPUVertexUniformData(
|
||||
s_state.ActiveCommandBuffer, 0,
|
||||
s_state.ActiveCamera ? s_state.ActiveCamera->GetProjectionMatrix() : &IdentityMatrix, sizeof(Mat4));
|
||||
SDL_PushGPUVertexUniformData(
|
||||
s_state.ActiveCommandBuffer, 1,
|
||||
(s_state.ActiveCamera && s_state.CameraRelative) ? s_state.ActiveCamera->GetViewMatrix() : &IdentityMatrix,
|
||||
sizeof(Mat4));
|
||||
}
|
||||
|
||||
VOID Renderer::EndFrame()
|
||||
@ -236,9 +232,9 @@ namespace ia::iae
|
||||
const auto activeScene = WorldManager::GetActiveScene();
|
||||
if (activeScene)
|
||||
{
|
||||
const auto sceneExtent = activeScene->Extent();
|
||||
s_state.SceneScaleFactor = {(FLOAT32) newWidth / (FLOAT32) sceneExtent.x,
|
||||
(FLOAT32) newHeight / (FLOAT32) sceneExtent.y};
|
||||
const auto sceneViewport = activeScene->Viewport();
|
||||
s_state.SceneScaleFactor = {(FLOAT32) newWidth / (FLOAT32) sceneViewport.x,
|
||||
(FLOAT32) newHeight / (FLOAT32) sceneViewport.y};
|
||||
IAE_LOG_INFO("Updated Scene Scale Factor: (", s_state.SceneScaleFactor.x, ", ", s_state.SceneScaleFactor.y,
|
||||
")");
|
||||
}
|
||||
@ -282,6 +278,11 @@ namespace ia::iae
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
SDL_PushGPUVertexUniformData(
|
||||
s_state.ActiveCommandBuffer, 1,
|
||||
(s_state.ActiveCamera && s_state.CameraRelative) ? s_state.ActiveCamera->GetViewMatrix() : &IdentityMatrix,
|
||||
sizeof(Mat4));
|
||||
|
||||
s_fragmentUniform.ColorOverlay = s_state.ColorOverlay.GetAsFloatVec();
|
||||
s_fragmentUniform.FlippedH = s_state.FlippedH;
|
||||
s_fragmentUniform.FlippedV = s_state.FlippedV;
|
||||
@ -385,7 +386,7 @@ namespace ia::iae
|
||||
sortIndex += static_cast<INT16>(position.y);
|
||||
|
||||
position *= Renderer::s_state.SceneScaleFactor;
|
||||
// scale *= Renderer::s_state.SceneScaleFactor;
|
||||
scale *= Renderer::s_state.SceneScaleFactor;
|
||||
|
||||
Renderer::s_state.ModelMatrix =
|
||||
glm::translate(glm::mat4(1.0f), glm::vec3{position.x, position.y,
|
||||
|
||||
@ -17,19 +17,137 @@
|
||||
#include <IAEngine/Engine.hpp>
|
||||
#include <IAEngine/Scene.hpp>
|
||||
|
||||
#include <IAEngine/pugixml/pugixml.hpp>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
// -----------------------------------------------
|
||||
// getInnerXML and xml_string_writer were taken from https://stackoverflow.com/a/60337372
|
||||
struct xml_string_writer : pugi::xml_writer
|
||||
{
|
||||
std::string result;
|
||||
|
||||
virtual void write(const void *data, size_t size)
|
||||
{
|
||||
result.append(static_cast<const char *>(data), size);
|
||||
}
|
||||
};
|
||||
|
||||
const auto getInnerXML = [](pugi::xml_node target) {
|
||||
xml_string_writer writer;
|
||||
for (pugi::xml_node child = target.first_child(); child; child = child.next_sibling())
|
||||
child.print(writer, "");
|
||||
return writer.result;
|
||||
};
|
||||
|
||||
// -----------------------------------------------
|
||||
|
||||
RefPtr<Scene> Scene::Create()
|
||||
{
|
||||
const auto scene = MakeRefPtr<Scene>();
|
||||
scene->Extent() = Engine::GetDisplayExtent();
|
||||
const auto cameraNode = MakeRefPtr<CameraNode>("MainCamera");
|
||||
scene->AddNode(cameraNode);
|
||||
scene->m_camera = cameraNode->GetCameraComponent();
|
||||
return scene;
|
||||
}
|
||||
|
||||
RefPtr<Scene> Scene::Create(IN CONST String &sceneXML)
|
||||
RefPtr<Scene> Scene::Create(IN CONST String &sceneXML,
|
||||
IN std::function<RefPtr<Node2D>(IN CONST String &, IN Handle)> getCustomNode,
|
||||
IN std::function<Handle(IN ResourceType type, IN CONST String &, IN INT64)> getResource)
|
||||
{
|
||||
const auto scene = MakeRefPtr<Scene>();
|
||||
scene->Extent() = Engine::GetDisplayExtent();
|
||||
const auto scene = Create();
|
||||
|
||||
pugi::xml_document doc;
|
||||
doc.load_string(sceneXML.c_str());
|
||||
const auto sceneRoot = doc.child("Scene");
|
||||
|
||||
Map<String, Handle> resources;
|
||||
|
||||
// Process Resources
|
||||
const auto resRoot = sceneRoot.child("Resources");
|
||||
if (!resRoot)
|
||||
THROW_INVALID_DATA("Scene file is missing 'Resources' tag");
|
||||
{
|
||||
for (const auto &t : resRoot.children())
|
||||
{
|
||||
if (!strcmp(t.name(), "Image"))
|
||||
{
|
||||
resources[t.attribute("name").as_string()] = getResource(
|
||||
ResourceType::IMAGE, t.attribute("path").as_string(), t.attribute("index").as_llong());
|
||||
}
|
||||
else if (!strcmp(t.name(), "Audio"))
|
||||
{
|
||||
resources[t.attribute("name").as_string()] =
|
||||
getResource(ResourceType::SOUND, t.attribute("path").as_string(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process Properties
|
||||
const auto propRoot = sceneRoot.child("Properties");
|
||||
if (!propRoot)
|
||||
THROW_INVALID_DATA("Scene file is missing 'Properties' tag");
|
||||
{
|
||||
auto t = propRoot.child("Extent");
|
||||
scene->Extent() = Vec2{t.attribute("width").as_float(), t.attribute("height").as_float()};
|
||||
|
||||
t = propRoot.child("Viewport");
|
||||
scene->Viewport() = Vec2{t.attribute("width").as_float(), t.attribute("height").as_float()};
|
||||
}
|
||||
|
||||
// Process Nodes
|
||||
const auto nodeRoot = sceneRoot.child("Nodes");
|
||||
if (!nodeRoot)
|
||||
THROW_INVALID_DATA("Scene file is missing 'Nodes' tag");
|
||||
{
|
||||
for (const auto &t : nodeRoot)
|
||||
{
|
||||
Node2D* n{};
|
||||
if (!strcmp(t.name(), "TextureNode"))
|
||||
{
|
||||
const auto node = MakeRefPtr<TextureNode>(Engine::GetUniqueResourceName());
|
||||
node->GetTextureComponent()->SetTexture(resources[t.attribute("texture").as_string()]);
|
||||
scene->AddNode(node);
|
||||
n = node.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
Handle id{INVALID_HANDLE};
|
||||
const auto attrID = t.attribute("id");
|
||||
if(attrID)
|
||||
id = (Handle)attrID.as_ullong();
|
||||
const auto node = getCustomNode(t.name(), id);
|
||||
scene->AddNode(node);
|
||||
n = node.get();
|
||||
}
|
||||
|
||||
if(!n) continue;
|
||||
if(t.attribute("X") && t.attribute("Y"))
|
||||
{
|
||||
n->SetLocalPosition({
|
||||
t.attribute("X").as_float(),
|
||||
t.attribute("Y").as_float()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process UI
|
||||
const auto uiRoot = sceneRoot.child("UI");
|
||||
if (!uiRoot)
|
||||
THROW_INVALID_DATA("Scene file is missing 'UI' tag");
|
||||
{
|
||||
scene->UIMarkupStyles() = getInnerXML(uiRoot.child("CSS")).c_str();
|
||||
auto html = String(getInnerXML(uiRoot.child("HTML")).c_str());
|
||||
html =
|
||||
Utils::RegexReplaceGroups(html, "<img(.*?)src=\"(.*?)\"", [&](IN INT32 index, IN CONST String &match) {
|
||||
if(index == 1)
|
||||
return BuildString("$H$", resources[match]);
|
||||
return match;
|
||||
});
|
||||
scene->UIMarkup() = html;
|
||||
}
|
||||
|
||||
return scene;
|
||||
}
|
||||
@ -72,4 +190,11 @@ namespace ia::iae
|
||||
{
|
||||
m_nodes[name] = nullptr;
|
||||
}
|
||||
|
||||
VOID Scene::OnActivate()
|
||||
{
|
||||
UI::SetMarkup(UIMarkup(), UIMarkupStyles());
|
||||
if(m_camera)
|
||||
Engine::SetActiveCamera(m_camera);
|
||||
}
|
||||
} // namespace ia::iae
|
||||
|
||||
@ -452,10 +452,11 @@ namespace ia::iae
|
||||
Rml::LoadFontFace(path.c_str());
|
||||
}
|
||||
|
||||
VOID UI::SetHTML(IN CONST String &source)
|
||||
VOID UI::SetMarkup(IN CONST String &markup, IN CONST String &styles)
|
||||
{
|
||||
g_document->SetStyleSheetContainer(Rml::Factory::InstanceStyleSheetString(styles.c_str()));
|
||||
g_document->SetInnerRML(
|
||||
BuildString("<body style=\"display: block; width: 100%; height: 100%;\">", source, "</body>").c_str());
|
||||
BuildString("<body style=\"display: block; width: 100%; height: 100%;\">", markup, "</body>").c_str());
|
||||
}
|
||||
|
||||
VOID UI::AddClickEvent(IN PCCHAR elementId, IN std::function<VOID()> callback)
|
||||
@ -502,8 +503,14 @@ namespace ia::iae
|
||||
|
||||
Rml::TextureHandle RmlUIRenderInterface::LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source)
|
||||
{
|
||||
return Engine::CreateImageFromFile(Engine::GetUniqueResourceName(), source.c_str(), texture_dimensions.x,
|
||||
texture_dimensions.y);
|
||||
Handle result;
|
||||
if (source.starts_with("$H$"))
|
||||
result = (Handle) std::stoi(source.substr(3));
|
||||
else
|
||||
result = Engine::CreateImageFromFile(Engine::GetUniqueResourceName(), source.c_str());
|
||||
const auto extent = Engine::GetImageExtent(result);
|
||||
texture_dimensions = {extent.x, extent.y};
|
||||
return result;
|
||||
}
|
||||
|
||||
Rml::TextureHandle RmlUIRenderInterface::GenerateTexture(Rml::Span<const Rml::byte> source,
|
||||
|
||||
@ -18,13 +18,15 @@
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
Vector<UINT8> Utils::Inflate(IN PCUINT8 data, IN SIZE_T dataSize)
|
||||
{
|
||||
STATIC UINT8 TMP_BUFFER[16384];
|
||||
Vector<UINT8> result;
|
||||
result.reserve(ia_min((SIZE_T)(dataSize * 3), sizeof(TMP_BUFFER)));
|
||||
result.reserve(ia_min((SIZE_T) (dataSize * 3), sizeof(TMP_BUFFER)));
|
||||
z_stream stream;
|
||||
stream.zalloc = Z_NULL;
|
||||
stream.zfree = Z_NULL;
|
||||
@ -35,7 +37,8 @@ namespace ia::iae
|
||||
THROW_UNKNOWN("Inflate failed: init zlib inflate");
|
||||
stream.avail_in = dataSize;
|
||||
stream.next_in = (Bytef *) data;
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
stream.avail_out = sizeof(TMP_BUFFER);
|
||||
stream.next_out = TMP_BUFFER;
|
||||
const auto r = inflate(&stream, Z_SYNC_FLUSH);
|
||||
@ -58,4 +61,35 @@ namespace ia::iae
|
||||
result.resize(deflateBound);
|
||||
return result;
|
||||
}
|
||||
|
||||
String Utils::RegexReplaceString(IN CONST String &input, IN CONST String &from, IN CONST String &to)
|
||||
{
|
||||
return std::regex_replace(input.c_str(), std::regex(from.c_str()), to.c_str()).c_str();
|
||||
}
|
||||
|
||||
String Utils::RegexReplaceGroups(IN CONST String &input, IN CONST String &pattern,
|
||||
IN std::function<String(IN INT32, IN CONST String &)> groupTransformer)
|
||||
{
|
||||
std::string text = input.c_str();
|
||||
|
||||
std::smatch match;
|
||||
std::string result;
|
||||
std::string::const_iterator search_start(text.cbegin());
|
||||
|
||||
SIZE_T t1 = 0, t2 = 0;
|
||||
while (std::regex_search(search_start, text.cend(), match, std::regex(pattern.c_str())))
|
||||
{
|
||||
for(SIZE_T i = 1; i < match.size(); i++)
|
||||
{
|
||||
result += text.substr(t1, match.position(i) - t1);
|
||||
result += std::string(groupTransformer(i - 1, match.str(i).c_str()).c_str());
|
||||
t1 = match.position(i);
|
||||
t2 = match.str(i).length();
|
||||
}
|
||||
result += text.substr(match.suffix().first - text.begin() - 1);
|
||||
search_start = match.suffix().first;
|
||||
}
|
||||
|
||||
return result.c_str();
|
||||
}
|
||||
} // namespace ia::iae
|
||||
@ -54,6 +54,7 @@ namespace ia::iae
|
||||
VOID WorldManager::ChangeActiveScene(IN RefPtr<Scene> scene)
|
||||
{
|
||||
s_activeScene = scene;
|
||||
scene->OnActivate();
|
||||
}
|
||||
|
||||
VOID WorldManager::AddNodeToActiveScene(IN RefPtr<INode> node)
|
||||
|
||||
@ -102,7 +102,8 @@ namespace ia::iae
|
||||
Vec4 Color{};
|
||||
};
|
||||
|
||||
enum class Direction : UINT8 {
|
||||
enum class Direction : UINT8
|
||||
{
|
||||
NONE = 255,
|
||||
DOWN = 0,
|
||||
DOWN_LEFT,
|
||||
@ -114,6 +115,12 @@ namespace ia::iae
|
||||
DOWN_RIGHT
|
||||
};
|
||||
|
||||
enum class ResourceType
|
||||
{
|
||||
IMAGE,
|
||||
SOUND,
|
||||
};
|
||||
|
||||
struct Color
|
||||
{
|
||||
UINT8 R{0xFF};
|
||||
|
||||
@ -45,10 +45,7 @@ namespace ia::iae
|
||||
return m_viewport;
|
||||
}
|
||||
|
||||
CONST Mat4 *GetViewMatrix() CONST
|
||||
{
|
||||
return &m_viewMatrix;
|
||||
}
|
||||
CONST Mat4 *GetViewMatrix();
|
||||
|
||||
CONST Mat4 *GetProjectionMatrix() CONST
|
||||
{
|
||||
@ -57,8 +54,8 @@ namespace ia::iae
|
||||
|
||||
private:
|
||||
Vec4 m_viewport{};
|
||||
Mat4 m_viewMatrix{1.0f};
|
||||
Vec2 m_positionOffset{};
|
||||
Mat4 m_viewMatrix{1.0f};
|
||||
Mat4 m_projectionMatrix{1.0f};
|
||||
};
|
||||
} // namespace ia::iae
|
||||
|
||||
@ -93,7 +93,6 @@ namespace ia::iae
|
||||
STATIC CameraComponent *GetActiveCamera();
|
||||
|
||||
// Scene Functions
|
||||
STATIC RefPtr<Scene> CreateSceneFromFile(IN CONST String &path);
|
||||
STATIC Scene *GetActiveScene();
|
||||
STATIC VOID ChangeActiveScene(IN RefPtr<Scene> scene);
|
||||
STATIC VOID AddNodeToActiveScene(IN RefPtr<INode> node);
|
||||
@ -118,6 +117,7 @@ namespace ia::iae
|
||||
STATIC VOID BindInputAxis(IN InputKey upKey, IN InputKey downKey, IN InputKey leftKey, IN InputKey rightKey);
|
||||
|
||||
// Utility Functions
|
||||
STATIC String ReadTextAsset(IN CONST String& path);
|
||||
STATIC Direction GetVectorPointingDirection(IN Vec2 v);
|
||||
STATIC Vector<UINT8> ReadBinaryAsset(IN CONST String& path);
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IAEngine/Components/CameraComponent.hpp>
|
||||
#include <IAEngine/Nodes/INode.hpp>
|
||||
|
||||
namespace ia::iae
|
||||
@ -24,7 +25,9 @@ namespace ia::iae
|
||||
{
|
||||
public:
|
||||
STATIC RefPtr<Scene> Create();
|
||||
STATIC RefPtr<Scene> Create(IN CONST String &sceneXML);
|
||||
STATIC RefPtr<Scene> Create(
|
||||
IN CONST String &sceneXML, IN std::function<RefPtr<Node2D>(IN CONST String &, IN Handle)> getCustomNode,
|
||||
IN std::function<Handle(IN ResourceType type, IN CONST String &, IN INT64)> getResource);
|
||||
|
||||
public:
|
||||
VOID AddNode(IN RefPtr<INode> node);
|
||||
@ -32,18 +35,37 @@ namespace ia::iae
|
||||
VOID RemoveNode(IN CONST String &name);
|
||||
|
||||
public:
|
||||
IVec2& Extent()
|
||||
String &UIMarkup()
|
||||
{
|
||||
return m_uiMarkup;
|
||||
}
|
||||
|
||||
String &UIMarkupStyles()
|
||||
{
|
||||
return m_uiMarkupStyles;
|
||||
}
|
||||
|
||||
IVec2 &Extent()
|
||||
{
|
||||
return m_extent;
|
||||
}
|
||||
|
||||
IVec2 &Viewport()
|
||||
{
|
||||
return m_viewport;
|
||||
}
|
||||
|
||||
Color &BackgroundColor()
|
||||
{
|
||||
return m_backgroundColor;
|
||||
}
|
||||
|
||||
private:
|
||||
String m_uiMarkup{};
|
||||
String m_uiMarkupStyles{};
|
||||
IVec2 m_extent{100, 100};
|
||||
IVec2 m_viewport{100, 100};
|
||||
CameraComponent *m_camera{};
|
||||
Color m_backgroundColor{0, 0, 0, 255};
|
||||
Map<String, RefPtr<INode>> m_nodes;
|
||||
|
||||
@ -53,5 +75,10 @@ namespace ia::iae
|
||||
|
||||
VOID FixedUpdate();
|
||||
VOID Update();
|
||||
|
||||
private:
|
||||
VOID OnActivate();
|
||||
|
||||
friend class WorldManager;
|
||||
};
|
||||
} // namespace ia::iae
|
||||
@ -25,7 +25,7 @@ namespace ia::iae
|
||||
public:
|
||||
STATIC VOID AddFontFromFile(IN CONST String &path);
|
||||
|
||||
STATIC VOID SetHTML(IN CONST String &source);
|
||||
STATIC VOID SetMarkup(IN CONST String &markup, IN CONST String &styles);
|
||||
|
||||
STATIC VOID AddClickEvent(IN PCCHAR elementId, IN std::function<VOID()> callback);
|
||||
|
||||
|
||||
@ -25,5 +25,8 @@ namespace ia::iae
|
||||
public:
|
||||
STATIC Vector<UINT8> Inflate(IN PCUINT8 data, IN SIZE_T dataSize);
|
||||
STATIC Vector<UINT8> Deflate(IN PCUINT8 data, IN SIZE_T dataSize);
|
||||
|
||||
STATIC String RegexReplaceString(IN CONST String& input, IN CONST String& from, IN CONST String& to);
|
||||
STATIC String RegexReplaceGroups(IN CONST String& input, IN CONST String& pattern, IN std::function<String(IN INT32, IN CONST String&)> groupTransformer);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user