The post Quake VR: Classic Shooter in Virtual Reality appeared first on VittorOmeo.
]]>The original Quake was a revolutionary FPS that set standards for the genre, such as fully three-dimensional levels, fast gameplay, and networked multiplayer. With the release of VR devices, enthusiastic developers created modifications to adapt the game for VR helmets. One of the most popular versions was Quake VR based on Quake 1 Darkplaces Engine, supporting Oculus Quest, Valve Index, HTC Vive and other devices.
If you are a fan of classic shooters and are looking for new emotions from a time-tested game – Quake VR is definitely worth a try. It’s a unique opportunity to travel back to the 90s, but with a modern level of immersion that makes the game even more exciting and dynamic.
Have you tried Quake in VR yet? Share your impressions!
The post Quake VR: Classic Shooter in Virtual Reality appeared first on VittorOmeo.
]]>The post Embracing Modern C++ Safely appeared first on VittorOmeo.
]]>Drawing on years of experience with large mission-critical projects, four leading C++ experts have categorized the language’s features into three groups:
This book gathers the C++ community’s experience with C++11/14 features and helps you make informed decisions that consider the real balance between safety, efficiency, and complexity in large-scale software projects. The author team has analyzed real code bases to objectively demonstrate the pros and cons of each innovation.
After reading this book, you will be able to:
The book will be useful for experienced C++ developers, team leaders, and technical managers who want to improve productivity, code quality, and long-term code maintenance.
Translated with DeepL.com (free version)
The post Embracing Modern C++ Safely appeared first on VittorOmeo.
]]>The post Continuations Without Memory Allocation appeared first on VittorOmeo.
]]>// pseudocode
auto f = when_all([]{ return http_get(“cat.com/nicecat.png”); }
[]{ return http_get(“dog.com/doggo.png”); })
.then([](auto p0, auto p1)
{
send_email(“mail@grandma.com”, combine(p0, p1))
});
f.execute(some_scheduler);
In this example, HTTP requests can be executed in parallel (depending on the scheduler’s operation, such as thread pooling). Once both requests are completed, a lambda function is automatically called with their results.
This model is convenient because it allows us to define oriented acyclic graphs of asynchronous computations in a clean and clear syntax. This is why the std::future standard is evolving to support .then and when_all. These features are included in the N4538 “Extensions for concurrency” proposal and were excellently presented by Anthony Williams in his talk “Concurrency, Parallelism and Coroutines” at ACCU 2016.
Consider the std::experimental::future::then signature:
template
auto std::experimental::future::then(F&&& func)
-> std::experimental::future< std::result_of_t(std::experimental::future)>
>;
The return type is again std::experimental::future, which means that the continuation is typed via future. A possible implementation of then might look like this:
template
auto then(std::experimental::future& parent, std::launch policy, F&&& f)
-> std::experimental::future< std::result_of_t&)>
>
{
return std::async(std::launch::async, [
parent = std::move(parent),
f = std::forward(f)
]
{
parent.wait();
return std::forward(f)(parent);
});
}
The use of std::async and type erasure hint at possible allocations. Let’s evaluate their value.
I wrote five .cpp files, using boost::async and boost::future, where I created a chain of .then continuations starting at zero and ending at four. For example:
// zero continuations
int main()
{
return boost::async([]{ return 123; }).get();
}
// one continuation
int main()
{
return boost::async([] { return 123; })
.then([](auto x) { return x.get() + 1; })
.get();
}
Instead of erasing types, you can preserve them by avoiding allocations and retaining the ability to compose asynchronous computations. Consider the following variant:
int main()
{
auto f = initiate([]{ return 1; })
.then([](int x){ return x + 1; })
.then([](int x){ return x + 1; });
f.execute(/* some scheduler */);
}
With a fully synchronous scheduler:
struct synchronous_scheduler
{
template
decltype(auto) operator()(F&&& f) const
{
return f();
}
};
The compiler generates only 2 lines of assembly code! And with std::async scheduler:
struct asynchronous_scheduler
{
template
decltype(auto) operator()(F&&& f) const
{
auto fut = std::async(f);
return fut.get();
}
};
891 lines of code are generated – but that number doesn’t grow when you add extensions, since the compiler can inline calls!
Function initiate:
template
auto initiate(F&&& f)
{
return node{std::forward(f)}
}
Template class node:
template
struct node : F
{
template
node(FFwd&&& f) : F{FWD(f)} {}
template <typename FThen>
auto then(FThen&&& f_then);
template <typename Scheduler>
decltype(auto) execute(Scheduler&& s) &
{
return s(*this);
}
};
Implementation .then:
template
auto then(FThen&&& f_then)
{
return node{[
parent = std::move(*this),
f_then = FWD(f_then)
]() mutable -> decltype(auto)
{
return f_then(static_cast(parent)());
}};
}
This approach eliminates allocations and simplifies compiler optimization. In the next article we will analyze when_all and thread pooling.
Thank you for your attention!
The post Continuations Without Memory Allocation appeared first on VittorOmeo.
]]>The post Capturing Perfectly-Forwarded Objects in Lambdas appeared first on VittorOmeo.
]]>This article explores the nuances of perfect forwarding within lambda captures, identifying potential pitfalls and presenting an elegant solution.
std::forward
in lambda captures can cause unexpected behavior.Consider the following struct definition:
struct A
{
int _value{0};
};
Now, let’s define a lambda function:
auto foo = [](auto& a)
{
return [&a]{ ++a._value; };
};
Using foo
with an instance of A
behaves as expected:
A my_a;
foo(my_a)();
std::cout << my_a._value << "\n"; // Prints `1`
Now, let’s generalize foo
to use perfect forwarding:
auto foo = [](auto&& a)
{
return [a = std::forward<decltype(a)>(a)]() mutable
{
++a._value;
std::cout << a._value << "\n";
};
};
While this works for rvalue references:
auto l_inner = foo(A{});
l_inner(); // Prints `1`
l_inner(); // Prints `2`
It fails for lvalue references:
A my_a;
auto l_inner = foo(my_a);
l_inner();
l_inner();
std::cout << my_a._value << "\n"; // Prints `0` (unexpected)
The issue lies in how lambda captures work. When we use a = std::forward<decltype(a)>(a)
, a
is always captured as a value, not a reference. This means mutations inside the lambda do not affect the original object.
To ensure the correct behavior, we introduce a wrapper class that can store either a reference or a value, depending on how it is initialized.
fwd_capture_wrapper
template <typename T>
struct fwd_capture_wrapper : impl::by_value<T>
{
using impl::by_value<T>::by_value;
};
// Specialized version for references
template <typename T>
struct fwd_capture_wrapper<T&> : impl::by_ref<T>
{
using impl::by_ref<T>::by_ref;
};
The implementation details of by_value
and by_ref
are:
template <typename T>
class by_value
{
private:
T _x;
public:
template <typename TFwd>
by_value(TFwd&& x) : _x{std::forward<TFwd>(x)} {}
auto& get() & { return _x; }
const auto& get() const& { return _x; }
auto get() && { return std::move(_x); }
};
For references, we use std::reference_wrapper
to avoid issues with copying:
template <typename T>
class by_ref
{
private:
std::reference_wrapper<T> _x;
public:
by_ref(T& x) : _x{x} {}
auto& get() & { return _x.get(); }
const auto& get() const& { return _x.get(); }
auto get() && { return std::move(_x.get()); }
};
We define a helper function for easy usage:
template <typename T>
auto fwd_capture(T&& x)
{
return fwd_capture_wrapper<T>(std::forward<T>(x));
}
foo
auto foo = [](auto&& a)
{
return [a = fwd_capture(std::forward<decltype(a)>(a))]() mutable
{
++a.get()._value;
std::cout << a.get()._value << "\n";
};
};
This ensures correct behavior for both lvalue and rvalue references.
Using std::forward
and fwd_capture
explicitly can be cumbersome. We define a macro:
#define FWD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)
#define FWD_CAPTURE(...) fwd_capture(FWD(__VA_ARGS__))
Now, we can simplify foo
:
auto foo = [](auto&& a)
{
return [a = FWD_CAPTURE(a)]() mutable { /* ... */ };
};
If foo
takes multiple arguments, capturing them correctly requires additional handling:
auto foo = [](auto&&... xs)
{
return [xs_pack = std::make_tuple(FWD_CAPTURE(xs)...)]() mutable
{
std::apply([](auto&&... xs)
{
((++xs.get()._value, std::cout << xs.get()._value << "\n"), ...);
}, xs_pack);
};
};
This allows foo
to handle multiple perfectly-forwarded arguments while maintaining reference semantics.
An even simpler approach is to directly use std::tuple
as the wrapper:
template <typename... Ts>
auto fwd_capture(Ts&&... xs)
{
return std::tuple<Ts...>(FWD(xs)...);
}
Accessing the captured values requires std::get
:
template <typename T>
decltype(auto) access(T&& x)
{
return std::get<0>(FWD(x));
}
This approach achieves the same goal with significantly less boilerplate.
Capturing perfectly-forwarded objects in lambdas requires careful handling to maintain the correct reference semantics. Using a wrapper like fwd_capture_wrapper
or leveraging std::tuple
provides an effective solution. Macros help reduce verbosity, and extending the approach to variadic arguments ensures flexibility.
By understanding these nuances, C++ developers can write more robust and efficient generic code when working with lambda captures and perfect forwarding.
The post Capturing Perfectly-Forwarded Objects in Lambdas appeared first on VittorOmeo.
]]>The post Therese Cameron appeared first on VittorOmeo.
]]>The post Therese Cameron appeared first on VittorOmeo.
]]>The post Zero-Overhead C++17 Currying & Partial Application appeared first on VittorOmeo.
]]>In this article, we will:
constexpr
zero-overhead curry function in C++17.Currying is a technique in which a function that takes multiple arguments is transformed into a sequence of functions, each taking a single argument.
A simple function with three parameters:
auto add3(int a, int b, int c) {
return a + b + c;
}
add3(1, 2, 3); // Returns 6.
A curried version:
auto curried_add3(int a) {
return [a](int b) {
return [a, b](int c) {
return a + b + c;
};
};
}
curried_add3(1)(2)(3); // Returns 6.
Currying allows for incremental argument binding, improving readability and reducing redundancy:
auto add2_one = curried_add3(1);
auto add1_three = add2_one(2);
add1_three(3); // Returns 6.
add1_three(4); // Returns 7.
This can be useful in practical scenarios such as filtering or searching through a collection:
std::vector<std::string> names{/* ... */};
auto find_in_names = curried_find(std::begin(names))(std::end(names));
auto jack = find_in_names("Jack");
auto rose = find_in_names("Rose");
Partial application refers to fixing a subset of a function’s arguments, returning another function with a reduced number of arguments.
partial_add3(1, 2, 3); // Returns 6.
partial_add3(1)(2, 3); // Returns 6.
partial_add3(1, 2)(3); // Returns 6.
partial_add3(1)(2)(3); // Returns 6. (Currying!)
This can be implemented in C++17 using recursion, variadic templates, if constexpr
, and fold expressions:
template <typename... Ts>
auto partial_add3(Ts... xs) {
static_assert(sizeof...(xs) <= 3);
if constexpr (sizeof...(xs) == 3) {
return (0 + ... + xs);
} else {
return [xs...](auto... ys) {
return partial_add3(xs..., ys...);
};
}
}
curry
FunctionWe aim to write a curry
function that:
constexpr
-friendly when applicable.template <typename TF>
constexpr decltype(auto) curry(TF&& f);
The return type decltype(auto)
ensures that the final function call retains the exact return type of the original function.
template <typename TF>
constexpr decltype(auto) curry(TF&& f) {
if constexpr (std::is_invocable_v<TF>) {
return std::forward<TF>(f)();
} else {
return [xf = std::forward<TF>(f)](auto&&... partials) mutable constexpr {
return curry([
partial_pack = std::tuple{std::forward<decltype(partials)>(partials)...},
yf = std::move(xf)
](auto&&... xs) constexpr -> decltype(auto) {
return std::apply([&yf](auto&&... ys) -> decltype(auto) {
return std::invoke(yf, std::forward<decltype(ys)>(ys)...);
}, std::tuple_cat(partial_pack, std::tuple{std::forward<decltype(xs)>(xs)...}));
});
};
}
}
curry
.std::apply
.To verify that curry
introduces no overhead, we compare generated assembly for direct calls vs. curried calls.
constexpr auto sum = [](auto a, auto b, auto c, auto d, auto e, auto f, auto g, auto h) constexpr {
return a + b + c + d + e + f + g + h;
};
constexpr auto expected = sum(0, 1, 2, 3, 4, 5, 6, 7);
constexpr auto s0 = curry(sum)(0, 1, 2, 3, 4, 5, 6, 7);
constexpr auto s1 = curry(sum)(0)(1, 2, 3, 4, 5, 6, 7);
Optimization Level | Baseline (Lines) | curry (Lines) | Overhead |
---|---|---|---|
O0 | 14 | 14 | 0% |
O1 | 2 | 2 | 0% |
O2 | 2 | 2 | 0% |
O3 | 2 | 2 | 0% |
Ofast | 2 | 2 | 0% |
No additional overhead is introduced when using curry
.
While curry
is efficient, it exposes some compiler limitations:
g++
crash with internal errors.clang++
has issues handling the recursion depth.These issues have been reported:
C++17 allows for a zero-overhead, generic curry
implementation that supports both currying and partial application. The assembly analysis confirms that modern compilers optimize curry
effectively. Despite some compiler issues, curry
is a powerful tool for functional programming in C++.
The post Zero-Overhead C++17 Currying & Partial Application appeared first on VittorOmeo.
]]>The post Download OpenHexagon v1.92 – Arcade Games for Hardcore Lovers appeared first on VittorOmeo.
]]>You can download OpenHexagon v1.92 for free on the following platforms:
If you like fast-paced arcades with hardcore gameplay and modding capabilities, OpenHexagon v1.92 is a great choice. Good luck with your playthrough!
The post Download OpenHexagon v1.92 – Arcade Games for Hardcore Lovers appeared first on VittorOmeo.
]]>The post OpenHexagon: a Dynamic Open Source Arcade Game appeared first on VittorOmeo.
]]>In OpenHexagon, the player controls a small triangle that rotates around a central point. The goal is simple – survive as long as possible by dodging approaching walls. Over time, the speed increases and the patterns become more complex, turning the game into a true test of reflexes.
Main features:
One of the key differences between OpenHexagon and the original Super Hexagon is the built-in level editor. Thanks to Lua scripts, users can create their own unique patterns, modify the game’s behavior, and even add new mechanics.
The game is an open-source project and is distributed under a free license. The source code is available on GitHub, and the developers encourage the community to participate in the development of the game, creating new levels and improving the mechanics.
If you like fast, challenging arcades, OpenHexagon is a great choice!
The post OpenHexagon: a Dynamic Open Source Arcade Game appeared first on VittorOmeo.
]]>The post VR and SFML: How Do They Fit Together? appeared first on VittorOmeo.
]]>SFML itself is not designed for VR, but it can be used as an auxiliary library. If the main goal is to develop a VR game or application, it is better to use OpenXR, OpenVR, or engines with VR support (such as Unity or Unreal Engine). However, if you need to add a 2D interface or multimedia features to a VR application, SFML can be useful.
The post VR and SFML: How Do They Fit Together? appeared first on VittorOmeo.
]]>The post ADT vs. Exceptions in Programming appeared first on VittorOmeo.
]]>Abstract Data Types (ADTs) in the context of error handling are usually special types such as Result or Option that explicitly encode the possibility of an error in a function signature.
An example in Rust:
rust
fn divide(a: f64, b: f64) -> Result {
if b == 0.0 {
Err(“Division by zero”.to_string()))
} else {
Ok(a / b)
}
}
In this example, the return type Result explicitly indicates that the function may terminate either successfully (Ok(f64)) or with an error (Err(String)). Thus, the compiler forces the programmer to handle possible errors.
Languages that support functional programming, such as Haskell or Scala, also use Either, Option, and similar constructs.
Advantages of ADT:
Disadvantages of ADTs:
Exceptions are a mechanism to interrupt normal program execution when an error occurs and pass control to an exception handler.
An example in Python:
python
def divide(a: float, b: float) -> float:
If b == 0:
raise ValueError(“Division by zero”)
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(f “Error: {e}”)
In this example, if b == 0, raise ValueError is called, which automatically passes control to the except block.
Advantages of exceptions:
Disadvantages of exceptions:
Use ADT if:
Use exceptions if:
The choice between ADT and exceptions depends on the project architecture and the peculiarities of the language you use. If your goal is strict error checking and explicit error representation, ADT (for example, Result and Option) will be the best choice. But if you are interested in code simplicity and natural integration with OOP, exceptions may be a more suitable tool.
The main thing is to apply the chosen approach consistently and not to mix them chaotically to avoid confusion in the code.
The post ADT vs. Exceptions in Programming appeared first on VittorOmeo.
]]>