Skip to content

Commit ea8ab36

Browse files
authored
Rework formatting for logs and fmt::format (geode-sdk#1890)
* Rework formatting * Update Formatter.hpp * Address the review
1 parent 353b012 commit ea8ab36

4 files changed

Lines changed: 492 additions & 212 deletions

File tree

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
#pragma once
2+
3+
#include <Geode/DefaultInclude.hpp>
4+
#include <Geode/utils/function.hpp>
5+
#include <Geode/utils/StringBuffer.hpp>
6+
#include <Geode/utils/cocos.hpp>
7+
#include <Geode/Result.hpp>
8+
#include <arc/future/PollableMetadata.hpp>
9+
#include <ccTypes.h>
10+
#include <string_view>
11+
#include <matjson.hpp>
12+
#include <type_traits>
13+
#include <fmt/core.h>
14+
// for formatting std::vector and such
15+
#include <fmt/ranges.h>
16+
// for std::optional
17+
#include <fmt/std.h>
18+
19+
namespace geode {
20+
// these are here because theyre special :-)
21+
22+
// these three have been made obsolete, but removing them would technically be a breaking change
23+
GEODE_DLL std::string format_as(cocos2d::CCObject const*);
24+
GEODE_DLL std::string format_as(cocos2d::CCArray*);
25+
GEODE_DLL std::string format_as(cocos2d::CCNode*);
26+
27+
class Mod;
28+
GEODE_DLL std::string format_as(Mod*);
29+
}
30+
31+
template <typename T, typename E>
32+
struct fmt::formatter<geode::Result<T, E>> {
33+
template <typename ParseContext>
34+
constexpr auto parse(ParseContext& ctx) noexcept { return ctx.begin(); }
35+
36+
template <typename FormatContext>
37+
auto format(geode::Result<T, E> const& result, FormatContext& ctx) const noexcept {
38+
std::string out;
39+
40+
auto formatValue = [&](auto&& value) {
41+
using V = std::decay_t<decltype(value)>;
42+
if constexpr (std::is_same_v<V, std::string>) {
43+
fmt::format_to(std::back_inserter(out), "\"{}\"", value);
44+
} else if constexpr (std::is_void_v<V>) {
45+
// nothing
46+
} else if constexpr (requires { geode::format_as(value); }) {
47+
fmt::format_to(std::back_inserter(out), "{}", geode::format_as(value));
48+
} else if constexpr (std::is_pointer_v<V>) {
49+
fmt::format_to(std::back_inserter(out), "{}", (void*)value);
50+
} else {
51+
fmt::format_to(std::back_inserter(out), "{}", value);
52+
}
53+
};
54+
55+
if (result) {
56+
std::string_view quotes = std::is_same_v<T, std::string> ? "\"" : "";
57+
if constexpr (!std::is_void_v<T>) {
58+
return fmt::format_to(ctx.out(), "Ok({}{}{})", quotes, result.unwrap(), quotes);
59+
}
60+
else {
61+
return fmt::format_to(ctx.out(), "Ok()");
62+
}
63+
} else {
64+
std::string_view quotes = std::is_same_v<E, std::string> ? "\"" : "";
65+
if constexpr (!std::is_void_v<E>) {
66+
return fmt::format_to(ctx.out(), "Err({}{}{})", quotes, result.unwrapErr(), quotes);
67+
}
68+
else {
69+
return fmt::format_to(ctx.out(), "Err()");
70+
}
71+
}
72+
}
73+
};
74+
75+
template <>
76+
struct fmt::formatter<cocos2d::ccColor3B> {
77+
template <typename ParseContext>
78+
constexpr auto parse(ParseContext& ctx) noexcept { return ctx.begin(); }
79+
80+
template <typename FormatContext>
81+
auto format(cocos2d::ccColor3B const& col, FormatContext& ctx) const noexcept {
82+
return fmt::format_to(ctx.out(), "rgb({}, {}, {})", col.r, col.g, col.b);
83+
}
84+
};
85+
86+
template <>
87+
struct fmt::formatter<cocos2d::ccColor4B> {
88+
template <typename ParseContext>
89+
constexpr auto parse(ParseContext& ctx) noexcept { return ctx.begin(); }
90+
91+
template <typename FormatContext>
92+
auto format(cocos2d::ccColor4B const& col, FormatContext& ctx) const noexcept {
93+
return fmt::format_to(ctx.out(), "rgba({}, {}, {}, {})", col.r, col.g, col.b, col.a);
94+
}
95+
};
96+
97+
template <>
98+
struct fmt::formatter<cocos2d::ccColor4F> {
99+
template <typename ParseContext>
100+
constexpr auto parse(ParseContext& ctx) noexcept { return ctx.begin(); }
101+
102+
template <typename FormatContext>
103+
auto format(cocos2d::ccColor4F const& col, FormatContext& ctx) const noexcept {
104+
return fmt::format_to(ctx.out(), "rgba({}, {}, {}, {})", col.r, col.g, col.b, col.a);
105+
}
106+
};
107+
108+
template <>
109+
struct fmt::formatter<cocos2d::CCPoint> {
110+
template <typename ParseContext>
111+
constexpr auto parse(ParseContext& ctx) noexcept { return ctx.begin(); }
112+
113+
template <typename FormatContext>
114+
auto format(cocos2d::CCPoint const& pt, FormatContext& ctx) const noexcept {
115+
return fmt::format_to(ctx.out(), "{}, {}", pt.x, pt.y);
116+
}
117+
};
118+
119+
template <>
120+
struct fmt::formatter<cocos2d::CCSize> {
121+
template <typename ParseContext>
122+
constexpr auto parse(ParseContext& ctx) noexcept { return ctx.begin(); }
123+
124+
template <typename FormatContext>
125+
auto format(cocos2d::CCSize const& sz, FormatContext& ctx) const noexcept {
126+
return fmt::format_to(ctx.out(), "{} : {}", sz.width, sz.height);
127+
}
128+
};
129+
130+
template <>
131+
struct fmt::formatter<cocos2d::CCRect> {
132+
template <typename ParseContext>
133+
constexpr auto parse(ParseContext& ctx) noexcept { return ctx.begin(); }
134+
135+
template <typename FormatContext>
136+
auto format(cocos2d::CCRect const& rect, FormatContext& ctx) const noexcept {
137+
return fmt::format_to(ctx.out(), "{} | {}", rect.origin, rect.size);
138+
}
139+
};
140+
141+
namespace geode::format {
142+
struct Wrapper {
143+
Wrapper(cocos2d::CCObject* obj) : m_obj(obj) {}
144+
cocos2d::CCObject* m_obj;
145+
};
146+
147+
class FormatBase {
148+
public:
149+
virtual ~FormatBase() = default;
150+
private:
151+
virtual geode::Result<std::string> format(cocos2d::CCObject const* obj, std::string_view specifier) = 0;
152+
friend class FormatterImpl;
153+
};
154+
155+
template<class T>
156+
class FormatType : public FormatBase {
157+
public:
158+
using FormatCallback = geode::Function<std::string(T*, std::string_view)>;
159+
160+
~FormatType() = default;
161+
FormatType(FormatCallback callback) : m_formatter(std::move(callback)) {}
162+
private:
163+
FormatCallback m_formatter;
164+
165+
geode::Result<std::string> format(cocos2d::CCObject const* obj, std::string_view specifier) override {
166+
if (!geode::cast::typeinfo_cast<T*>(obj)) return geode::Err("Incorrect Type");
167+
if (!m_formatter) return geode::Err("No formatter callback");
168+
return geode::Ok(m_formatter(static_cast<T*>(const_cast<cocos2d::CCObject*>(obj)), specifier));
169+
}
170+
171+
friend class FormatterImpl;
172+
};
173+
174+
GEODE_DLL void registerFormatImpl(std::string_view name, std::unique_ptr<FormatBase> format);
175+
GEODE_DLL geode::Result<std::string> handleFormatImpl(cocos2d::CCObject const* obj, std::string_view specifier);
176+
177+
template<class T, typename F>
178+
inline void registerFormat(F&& callback) {
179+
constexpr auto name = arc::getTypename<T>();
180+
181+
using Callback = typename FormatType<T>::FormatCallback;
182+
183+
if constexpr (std::is_invocable_r_v<std::string, F, T*, std::string_view>) {
184+
registerFormatImpl(std::string_view{name.first, name.second}, std::make_unique<FormatType<T>>(std::forward<F>(callback)));
185+
}
186+
else if constexpr (std::is_invocable_r_v<std::string, F, T*>) {
187+
registerFormatImpl(std::string_view{name.first, name.second}, std::make_unique<FormatType<T>>(Callback(
188+
[f = std::forward<F>(callback)](T* obj, std::string_view) {
189+
return f(obj);
190+
}
191+
)));
192+
}
193+
else {
194+
static_assert(
195+
false,
196+
"Format callback must be invocable as std::string(T*) or std::string(T*, std::string_view)"
197+
);
198+
}
199+
}
200+
201+
template <class T>
202+
concept CocosPtr =
203+
std::is_pointer_v<std::decay_t<T>> &&
204+
std::is_base_of_v<
205+
cocos2d::CCObject,
206+
std::remove_pointer_t<std::decay_t<T>>
207+
>;
208+
209+
// allow wrapping CCObjects so they can be caught by the formatter
210+
template <class T>
211+
decltype(auto) wrap(T&& value) {
212+
if constexpr (CocosPtr<T>) {
213+
return Wrapper{ value };
214+
} else {
215+
return std::forward<T>(value);
216+
}
217+
}
218+
219+
template <class T>
220+
using TransformType = std::conditional_t<
221+
CocosPtr<T>,
222+
decltype(wrap<T>(std::declval<T>())),
223+
T
224+
>;
225+
226+
template <class... Args>
227+
using FmtStr = fmt::format_string<TransformType<Args>...>;
228+
}
229+
230+
template <>
231+
struct fmt::formatter<geode::format::Wrapper> {
232+
std::string specifier;
233+
234+
template <typename ParseContext>
235+
constexpr auto parse(ParseContext& ctx) noexcept {
236+
auto begin = ctx.begin();
237+
auto end = ctx.end();
238+
239+
auto it = begin;
240+
while (it != end && *it != '}') ++it;
241+
242+
specifier = std::string_view(begin, it - begin);
243+
return it;
244+
}
245+
246+
template <typename FormatContext>
247+
auto format(geode::format::Wrapper const& wrapper, FormatContext& ctx) const noexcept {
248+
if (!wrapper.m_obj) return fmt::format_to(ctx.out(), "{}", "{ CCObject, null }");
249+
250+
auto result = geode::format::handleFormatImpl(wrapper.m_obj, specifier);
251+
if (result) return fmt::format_to(ctx.out(), "{{ {} ({}), {} }}", geode::cocos::getObjectName(wrapper.m_obj), wrapper.m_obj->getTag(), result.unwrap());
252+
253+
return fmt::format_to(ctx.out(), "{{ {} ({}), {} }}", geode::cocos::getObjectName(wrapper.m_obj), wrapper.m_obj->getTag(), fmt::ptr(wrapper.m_obj));
254+
}
255+
};

0 commit comments

Comments
 (0)