├── .gitignore
├── LICENCE.md
├── README.md
├── contributions
└── README.md
├── examples
├── TEST_Animate2D.cpp
├── TEST_Camera2D.cpp
├── TEST_Hardware3D.cpp
├── TEST_QuickGUI.cpp
└── TEST_Shaders.cpp
├── extensions
├── olcPGEX_Graphics2D.h
├── olcPGEX_Graphics3D.h
├── olcPGEX_Network.h
├── olcPGEX_PopUpMenu.h
├── olcPGEX_QuickGUI.h
├── olcPGEX_RayCastWorld.h
├── olcPGEX_Shaders.h
├── olcPGEX_Sound.h
├── olcPGEX_SplashScreen.h
├── olcPGEX_TransformedView.h
└── olcPGEX_Wireframe.h
├── olcExampleProgram.cpp
├── olcPixelGameEngine.h
├── tools
└── wasm
│ ├── README.md
│ ├── basic_template.html
│ └── pge2wasm.bat
└── utilities
├── olcUTIL_Animate2D.h
├── olcUTIL_Camera2D.h
├── olcUTIL_Container.h
├── olcUTIL_DataFile.h
├── olcUTIL_Geometry2D.h
├── olcUTIL_Hardware3D.h
├── olcUTIL_Palette.h
└── olcUTIL_QuadTree.h
/.gitignore:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio.
3 | ################################################################################
4 |
5 | /.vs
6 | /utilities/olcUTIL_AffineView.h
7 | /utilities/olcUTIL_Maths.h
8 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | # License (OLC-3)
2 |
3 | Copyright 2018-2023 OneLoneCoder.com
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions
7 | are met:
8 |
9 | 1. Redistributions or derivations of source code must retain the above
10 | copyright notice, this list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions or derivative works in binary form must reproduce
13 | the above copyright notice. This list of conditions and the following
14 | disclaimer must be reproduced in the documentation and/or other
15 | materials provided with the distribution.
16 |
17 | 3. Neither the name of the copyright holder nor the names of its
18 | contributors may be used to endorse or promote products derived
19 | from this software without specific prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hang on! I'm looking for Javidx9's video source code! It's moved here: https://github.com/OneLoneCoder/Javidx9
5 |
6 | # Shameless Promotion
7 | There is a feature rich and working fork of this repo aimed at Android & IOS development: https://github.com/Johnnyg63/OLCPGEMobileVisualStudio
8 |
9 | # olcPixelGameEngine
10 | The official distribution of olcPixelGameEngine, a tool used in javidx9's YouTube videos and projects.
11 |
12 | **You only need the one file - olcPixelGameEngine.h - included in your project!**
13 |
14 | Provides a fast, richly featured, cross platform pixel drawing and user interface framework for
15 | * The development of games
16 | * Visualisation of algorithms
17 | * Prototyping and experimentation
18 | * Education
19 |
20 | olcPixelGameEngine is easily extended! for example:
21 | * 2D Affine transforms
22 | * 3D Software renderer
23 | * Controller input
24 | * Sound
25 | * Hardware interfaces
26 |
27 | olcPixelGameEngine is easy to port! Runs on:
28 | * Windows (all)
29 | * Linux / Raspberry Pi / ChromeOS
30 | * MacOS (coming soon to official, but already available in "Contributors")
31 | * PSP & Switch (Not supported by OneLoneCoder)
32 |
33 | olcPixelGameEngine has been reimplemented in other languages!
34 | * C#
35 | * Rust
36 | * Lua
37 | * Java
38 |
39 | olcPixelGameEngine is actively maintained and developed!
40 |
41 | olcPixelGameEngine is used by 100s, if not 1000s of programmers at all levels of ability!
42 |
43 |
44 | # Documentation
45 | Please see https://github.com/OneLoneCoder/olcPixelGameEngine/wiki
46 |
47 | # License (OLC-3)
48 | Copyright 2018 - 2024 OneLoneCoder.com
49 |
50 | Redistribution and use in source and binary forms, with or without
51 | modification, are permitted provided that the following conditions
52 | are met:
53 |
54 | 1. Redistributions or derivations of source code must retain the above
55 | copyright notice, this list of conditions and the following disclaimer.
56 |
57 | 2. Redistributions or derivative works in binary form must reproduce
58 | the above copyright notice. This list of conditions and the following
59 | disclaimer must be reproduced in the documentation and/or other
60 | materials provided with the distribution.
61 |
62 | 3. Neither the name of the copyright holder nor the names of its
63 | contributors may be used to endorse or promote products derived
64 | from this software without specific prior written permission.
65 |
66 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
67 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
68 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
69 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
70 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
71 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
72 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
73 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
74 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
75 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
76 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
77 |
--------------------------------------------------------------------------------
/contributions/README.md:
--------------------------------------------------------------------------------
1 | # 3rd Party Contributions
2 |
3 | These source code contributions enhance the functionality of the olcPixelGameEngine header file. They are not supported by OneLoneCoder.com or javidx9 so use them at your own risk! Though this is a nice community and to get listed here you have to be trusted...
4 |
5 | ## PixelGameEngine Extensions (PGEX)
6 | * Adds a "stencil buffer" like viewport to drawing functions
7 | * https://github.com/gorbit99/olcPGEX_ViewPort
8 | * Cross Platform Controller Support
9 | * https://github.com/gorbit99/olcPGEX_Gamepad
10 | * General Font Rendering
11 | * https://github.com/gorbit99/olcPGEX_TTF
12 | * Widens support for audio files into olcPGEX_Sound.h
13 | * https://github.com/gorbit99/olcPGEX_AudioConvert
14 | * Sprite Animation, Sprite State Machine and Sprite Sheet Manipulation
15 | * https://github.com/matt-hayward/olcPGEX_AnimatedSprite
16 | * Additional colour constants
17 | * https://github.com/matt-hayward/olcPGEX_AdditionalColours
18 | * Various Sprite Handling & Camera Control Utilities
19 | * https://github.com/justinrichardsmusic/PGEv2_Extensions
20 | * DearImGUI Integration
21 | * https://github.com/dandistine/olcPGEDearImGui
22 | * Pre-loaded Font Rendering
23 | * https://github.com/Oso-Grande/olcPGEX_Font
24 | * Arc Drawing
25 | * https://github.com/AlterEgoIV/olcPGEX_Arc
26 |
27 | ## MacOS Support
28 | * These will potentially be absorbed into main build
29 | * https://github.com/MumflrFumperdink/olcPGEMac
30 |
31 | ## Android & IOS Support
32 | * Fiddlier to setup, but pretty cool once going
33 | * https://github.com/Johnnyg63/OLCPGEMobileVisualStudio
34 |
35 | ## Build Systems
36 | * Meson (https://mesonbuild.com/index.html)
37 | * https://github.com/jpakkane/pixeldemo
38 | * CMake script
39 | * https://github.com/plane000/olcPixelGameEngine/blob/master/CMakeLists.txt
40 | * CMake script for all includes Emscripten/WASM/Webby stuff
41 | * https://github.com/L0huis/PGE-CMake
42 |
43 | ## Utilities
44 | * Additional fonts and font handling tools
45 | * https://github.com/gorbit99/OLC-Font
46 | * Convert olcConsoleGameEngine ".spr" files into olc::Sprite types
47 | * https://github.com/gorbit99/SprConverter
48 |
49 | ## Customisations
50 | * Version with SDL backend, and native controller support
51 | * https://github.com/Allersnj/olcPixelGameEngineSDL
52 |
53 | ## Cool Projects
54 | Have you made something using olcPixelGameEngine? Contact me to get a link to it here!
55 |
--------------------------------------------------------------------------------
/examples/TEST_Animate2D.cpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Animate2D.cpp
--------------------------------------------------------------------------------
/examples/TEST_Camera2D.cpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Camera2D.cpp
--------------------------------------------------------------------------------
/examples/TEST_Hardware3D.cpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Hardware3D.cpp
--------------------------------------------------------------------------------
/examples/TEST_QuickGUI.cpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_QuickGUI.cpp
--------------------------------------------------------------------------------
/examples/TEST_Shaders.cpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcPixelGameEngine/66e59827f281ba7f7be6d5debb200eaf3d33a746/examples/TEST_Shaders.cpp
--------------------------------------------------------------------------------
/extensions/olcPGEX_Graphics2D.h:
--------------------------------------------------------------------------------
1 | /*
2 | olcPGEX_Graphics2D.h
3 |
4 | +-------------------------------------------------------------+
5 | | OneLoneCoder Pixel Game Engine Extension |
6 | | Advanced 2D Rendering - v0.5 |
7 | +-------------------------------------------------------------+
8 |
9 | What is this?
10 | ~~~~~~~~~~~~~
11 | This is an extension to the olcPixelGameEngine, which provides
12 | advanced olc::Sprite manipulation and drawing routines. To use
13 | it, simply include this header file.
14 |
15 | License (OLC-3)
16 | ~~~~~~~~~~~~~~~
17 |
18 | Copyright 2018 - 2019 OneLoneCoder.com
19 |
20 | Redistribution and use in source and binary forms, with or without
21 | modification, are permitted provided that the following conditions
22 | are met:
23 |
24 | 1. Redistributions or derivations of source code must retain the above
25 | copyright notice, this list of conditions and the following disclaimer.
26 |
27 | 2. Redistributions or derivative works in binary form must reproduce
28 | the above copyright notice. This list of conditions and the following
29 | disclaimer must be reproduced in the documentation and/or other
30 | materials provided with the distribution.
31 |
32 | 3. Neither the name of the copyright holder nor the names of its
33 | contributors may be used to endorse or promote products derived
34 | from this software without specific prior written permission.
35 |
36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
37 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
40 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 |
48 | Links
49 | ~~~~~
50 | YouTube: https://www.youtube.com/javidx9
51 | Discord: https://discord.gg/WhwHUMV
52 | Twitter: https://www.twitter.com/javidx9
53 | Twitch: https://www.twitch.tv/javidx9
54 | GitHub: https://www.github.com/onelonecoder
55 | Homepage: https://www.onelonecoder.com
56 |
57 | Author
58 | ~~~~~~
59 | David Barr, aka javidx9, ©OneLoneCoder 2019
60 | */
61 |
62 | /*
63 | Matrices stored as [Column][Row] (i.e. x, y)
64 |
65 | |C0R0 C1R0 C2R0| | x | | x'|
66 | |C0R1 C1R1 C2R1| * | y | = | y'|
67 | |C0R2 C1R2 C2R2| |1.0| | - |
68 | */
69 |
70 |
71 |
72 | #ifndef OLC_PGEX_GFX2D
73 | #define OLC_PGEX_GFX2D
74 |
75 | #include
76 |
77 | #include "olcPixelGameEngine.h"
78 |
79 | #undef min
80 | #undef max
81 |
82 | namespace olc
83 | {
84 | // Container class for Advanced 2D Drawing functions
85 | class GFX2D : public olc::PGEX
86 | {
87 | // A representation of an affine transform, used to rotate, scale, offset & shear space
88 | public:
89 | class Transform2D
90 | {
91 | public:
92 | Transform2D();
93 |
94 | public:
95 | // Set this transformation to unity
96 | void Reset();
97 | // Append a rotation of fTheta radians to this transform
98 | void Rotate(float fTheta);
99 | // Append a translation (ox, oy) to this transform
100 | void Translate(float ox, float oy);
101 | // Append a scaling operation (sx, sy) to this transform
102 | void Scale(float sx, float sy);
103 | // Append a shear operation (sx, sy) to this transform
104 | void Shear(float sx, float sy);
105 |
106 | void Perspective(float ox, float oy);
107 | // Calculate the Forward Transformation of the coordinate (in_x, in_y) -> (out_x, out_y)
108 | void Forward(float in_x, float in_y, float &out_x, float &out_y);
109 | // Calculate the Inverse Transformation of the coordinate (in_x, in_y) -> (out_x, out_y)
110 | void Backward(float in_x, float in_y, float &out_x, float &out_y);
111 | // Regenerate the Inverse Transformation
112 | void Invert();
113 |
114 | private:
115 | void Multiply();
116 | float matrix[4][3][3];
117 | int nTargetMatrix;
118 | int nSourceMatrix;
119 | bool bDirty;
120 | };
121 |
122 | public:
123 | // Draws a sprite with the transform applied
124 | static void DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform);
125 | };
126 | }
127 |
128 |
129 | #ifdef OLC_PGEX_GRAPHICS2D
130 | #undef OLC_PGEX_GRAPHICS2D
131 |
132 | namespace olc
133 | {
134 | void GFX2D::DrawSprite(olc::Sprite *sprite, olc::GFX2D::Transform2D &transform)
135 | {
136 | if (sprite == nullptr)
137 | return;
138 |
139 | // Work out bounding rectangle of sprite
140 | float ex, ey;
141 | float sx, sy;
142 | float px, py;
143 |
144 | transform.Forward(0.0f, 0.0f, sx, sy);
145 | px = sx; py = sy;
146 | sx = std::min(sx, px); sy = std::min(sy, py);
147 | ex = std::max(ex, px); ey = std::max(ey, py);
148 |
149 | transform.Forward((float)sprite->width, (float)sprite->height, px, py);
150 | sx = std::min(sx, px); sy = std::min(sy, py);
151 | ex = std::max(ex, px); ey = std::max(ey, py);
152 |
153 | transform.Forward(0.0f, (float)sprite->height, px, py);
154 | sx = std::min(sx, px); sy = std::min(sy, py);
155 | ex = std::max(ex, px); ey = std::max(ey, py);
156 |
157 | transform.Forward((float)sprite->width, 0.0f, px, py);
158 | sx = std::min(sx, px); sy = std::min(sy, py);
159 | ex = std::max(ex, px); ey = std::max(ey, py);
160 |
161 | // Perform inversion of transform if required
162 | transform.Invert();
163 |
164 | if (ex < sx)
165 | std::swap(ex, sx);
166 | if (ey < sy)
167 | std::swap(ey, sy);
168 |
169 | // Iterate through render space, and sample Sprite from suitable texel location
170 | for (float i = sx; i < ex; i++)
171 | {
172 | for (float j = sy; j < ey; j++)
173 | {
174 | float ox, oy;
175 | transform.Backward(i, j, ox, oy);
176 | pge->Draw((int32_t)i, (int32_t)j, sprite->GetPixel((int32_t)(ox+0.5f), (int32_t)(oy+0.5f)));
177 | }
178 | }
179 | }
180 |
181 | olc::GFX2D::Transform2D::Transform2D()
182 | {
183 | Reset();
184 | }
185 |
186 | void olc::GFX2D::Transform2D::Reset()
187 | {
188 | nTargetMatrix = 0;
189 | nSourceMatrix = 1;
190 | bDirty = true;
191 |
192 | // Columns Then Rows
193 |
194 | // Matrices 0 & 1 are used as swaps in Transform accumulation
195 | matrix[0][0][0] = 1.0f; matrix[0][1][0] = 0.0f; matrix[0][2][0] = 0.0f;
196 | matrix[0][0][1] = 0.0f; matrix[0][1][1] = 1.0f; matrix[0][2][1] = 0.0f;
197 | matrix[0][0][2] = 0.0f; matrix[0][1][2] = 0.0f; matrix[0][2][2] = 1.0f;
198 |
199 | matrix[1][0][0] = 1.0f; matrix[1][1][0] = 0.0f; matrix[1][2][0] = 0.0f;
200 | matrix[1][0][1] = 0.0f; matrix[1][1][1] = 1.0f; matrix[1][2][1] = 0.0f;
201 | matrix[1][0][2] = 0.0f; matrix[1][1][2] = 0.0f; matrix[1][2][2] = 1.0f;
202 |
203 | // Matrix 2 is a cache matrix to hold the immediate transform operation
204 | // Matrix 3 is a cache matrix to hold the inverted transform
205 | }
206 |
207 | void olc::GFX2D::Transform2D::Multiply()
208 | {
209 | for (int c = 0; c < 3; c++)
210 | {
211 | for (int r = 0; r < 3; r++)
212 | {
213 | matrix[nTargetMatrix][c][r] = matrix[2][0][r] * matrix[nSourceMatrix][c][0] +
214 | matrix[2][1][r] * matrix[nSourceMatrix][c][1] +
215 | matrix[2][2][r] * matrix[nSourceMatrix][c][2];
216 | }
217 | }
218 |
219 | std::swap(nTargetMatrix, nSourceMatrix);
220 | bDirty = true; // Any transform multiply dirties the inversion
221 | }
222 |
223 | void olc::GFX2D::Transform2D::Rotate(float fTheta)
224 | {
225 | // Construct Rotation Matrix
226 | matrix[2][0][0] = cosf(fTheta); matrix[2][1][0] = sinf(fTheta); matrix[2][2][0] = 0.0f;
227 | matrix[2][0][1] = -sinf(fTheta); matrix[2][1][1] = cosf(fTheta); matrix[2][2][1] = 0.0f;
228 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
229 | Multiply();
230 | }
231 |
232 | void olc::GFX2D::Transform2D::Scale(float sx, float sy)
233 | {
234 | // Construct Scale Matrix
235 | matrix[2][0][0] = sx; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
236 | matrix[2][0][1] = 0.0f; matrix[2][1][1] = sy; matrix[2][2][1] = 0.0f;
237 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
238 | Multiply();
239 | }
240 |
241 | void olc::GFX2D::Transform2D::Shear(float sx, float sy)
242 | {
243 | // Construct Shear Matrix
244 | matrix[2][0][0] = 1.0f; matrix[2][1][0] = sx; matrix[2][2][0] = 0.0f;
245 | matrix[2][0][1] = sy; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
246 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
247 | Multiply();
248 | }
249 |
250 | void olc::GFX2D::Transform2D::Translate(float ox, float oy)
251 | {
252 | // Construct Translate Matrix
253 | matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = ox;
254 | matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = oy;
255 | matrix[2][0][2] = 0.0f; matrix[2][1][2] = 0.0f; matrix[2][2][2] = 1.0f;
256 | Multiply();
257 | }
258 |
259 | void olc::GFX2D::Transform2D::Perspective(float ox, float oy)
260 | {
261 | // Construct Translate Matrix
262 | matrix[2][0][0] = 1.0f; matrix[2][1][0] = 0.0f; matrix[2][2][0] = 0.0f;
263 | matrix[2][0][1] = 0.0f; matrix[2][1][1] = 1.0f; matrix[2][2][1] = 0.0f;
264 | matrix[2][0][2] = ox; matrix[2][1][2] = oy; matrix[2][2][2] = 1.0f;
265 | Multiply();
266 | }
267 |
268 | void olc::GFX2D::Transform2D::Forward(float in_x, float in_y, float &out_x, float &out_y)
269 | {
270 | out_x = in_x * matrix[nSourceMatrix][0][0] + in_y * matrix[nSourceMatrix][1][0] + matrix[nSourceMatrix][2][0];
271 | out_y = in_x * matrix[nSourceMatrix][0][1] + in_y * matrix[nSourceMatrix][1][1] + matrix[nSourceMatrix][2][1];
272 | float out_z = in_x * matrix[nSourceMatrix][0][2] + in_y * matrix[nSourceMatrix][1][2] + matrix[nSourceMatrix][2][2];
273 | if (out_z != 0)
274 | {
275 | out_x /= out_z;
276 | out_y /= out_z;
277 | }
278 | }
279 |
280 | void olc::GFX2D::Transform2D::Backward(float in_x, float in_y, float &out_x, float &out_y)
281 | {
282 | out_x = in_x * matrix[3][0][0] + in_y * matrix[3][1][0] + matrix[3][2][0];
283 | out_y = in_x * matrix[3][0][1] + in_y * matrix[3][1][1] + matrix[3][2][1];
284 | float out_z = in_x * matrix[3][0][2] + in_y * matrix[3][1][2] + matrix[3][2][2];
285 | if (out_z != 0)
286 | {
287 | out_x /= out_z;
288 | out_y /= out_z;
289 | }
290 | }
291 |
292 | void olc::GFX2D::Transform2D::Invert()
293 | {
294 | if (bDirty) // Obviously costly so only do if needed
295 | {
296 | float det = matrix[nSourceMatrix][0][0] * (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) -
297 | matrix[nSourceMatrix][1][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2]) +
298 | matrix[nSourceMatrix][2][0] * (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][0][2]);
299 |
300 | float idet = 1.0f / det;
301 | matrix[3][0][0] = (matrix[nSourceMatrix][1][1] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][1][2] * matrix[nSourceMatrix][2][1]) * idet;
302 | matrix[3][1][0] = (matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][2]) * idet;
303 | matrix[3][2][0] = (matrix[nSourceMatrix][1][0] * matrix[nSourceMatrix][2][1] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][1][1]) * idet;
304 | matrix[3][0][1] = (matrix[nSourceMatrix][2][1] * matrix[nSourceMatrix][0][2] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][2]) * idet;
305 | matrix[3][1][1] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][2] - matrix[nSourceMatrix][2][0] * matrix[nSourceMatrix][0][2]) * idet;
306 | matrix[3][2][1] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][2][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][2][1]) * idet;
307 | matrix[3][0][2] = (matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][2] - matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][1]) * idet;
308 | matrix[3][1][2] = (matrix[nSourceMatrix][0][2] * matrix[nSourceMatrix][1][0] - matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][2]) * idet;
309 | matrix[3][2][2] = (matrix[nSourceMatrix][0][0] * matrix[nSourceMatrix][1][1] - matrix[nSourceMatrix][0][1] * matrix[nSourceMatrix][1][0]) * idet;
310 | bDirty = false;
311 | }
312 | }
313 | }
314 |
315 | #endif
316 | #endif
--------------------------------------------------------------------------------
/extensions/olcPGEX_Network.h:
--------------------------------------------------------------------------------
1 | /*
2 | ASIO Based Networking olcPixelGameEngine Extension v1.0
3 |
4 | Videos:
5 | Part #1: https://youtu.be/2hNdkYInj4g
6 | Part #2: https://youtu.be/UbjxGvrDrbw
7 | Part #3: https://youtu.be/hHowZ3bWsio
8 | Part #4: https://youtu.be/f_1lt9pfaEo
9 |
10 | License (OLC-3)
11 | ~~~~~~~~~~~~~~~
12 |
13 | Copyright 2018 - 2021 OneLoneCoder.com
14 |
15 | Redistribution and use in source and binary forms, with or without
16 | modification, are permitted provided that the following conditions
17 | are met:
18 |
19 | 1. Redistributions or derivations of source code must retain the above
20 | copyright notice, this list of conditions and the following disclaimer.
21 |
22 | 2. Redistributions or derivative works in binary form must reproduce
23 | the above copyright notice. This list of conditions and the following
24 | disclaimer must be reproduced in the documentation and/or other
25 | materials provided with the distribution.
26 |
27 | 3. Neither the name of the copyright holder nor the names of its
28 | contributors may be used to endorse or promote products derived
29 | from this software without specific prior written permission.
30 |
31 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 |
43 | Links
44 | ~~~~~
45 | YouTube: https://www.youtube.com/javidx9
46 | Discord: https://discord.gg/WhwHUMV
47 | Twitter: https://www.twitter.com/javidx9
48 | Twitch: https://www.twitch.tv/javidx9
49 | GitHub: https://www.github.com/onelonecoder
50 | Homepage: https://www.onelonecoder.com
51 |
52 | Author
53 | ~~~~~~
54 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021
55 |
56 | */
57 |
58 | #pragma once
59 |
60 | #include
61 | #include
62 | #include
63 | #include
64 | #include
65 | #include
66 | #include
67 | #include
68 | #include
69 | #include
70 |
71 | #ifdef _WIN32
72 | #ifndef _WIN32_WINNT
73 | #define _WIN32_WINNT 0x0A00
74 | #endif
75 | #endif
76 |
77 | #define _WINSOCK_DEPRECATED_NO_WARNINGS
78 | #define ASIO_STANDALONE
79 | #include
80 | #include
81 | #include
82 |
83 | namespace olc
84 | {
85 | namespace net
86 | {
87 | // Message
88 |
89 | // Message Header is sent at start of all messages. The template allows us
90 | // to use "enum class" to ensure that the messages are valid at compile time
91 | template
92 | struct message_header
93 | {
94 | T id{};
95 | uint32_t size = 0;
96 | };
97 |
98 | // Message Body contains a header and a std::vector, containing raw bytes
99 | // of infomation. This way the message can be variable length, but the size
100 | // in the header must be updated.
101 | template
102 | struct message
103 | {
104 | // Header & Body vector
105 | message_header header{};
106 | std::vector body;
107 |
108 | // returns size of entire message packet in bytes
109 | size_t size() const
110 | {
111 | return body.size();
112 | }
113 |
114 | // Override for std::cout compatibility - produces friendly description of message
115 | friend std::ostream& operator << (std::ostream& os, const message& msg)
116 | {
117 | os << "ID:" << int(msg.header.id) << " Size:" << msg.header.size;
118 | return os;
119 | }
120 |
121 | // Convenience Operator overloads - These allow us to add and remove stuff from
122 | // the body vector as if it were a stack, so First in, Last Out. These are a
123 | // template in itself, because we dont know what data type the user is pushing or
124 | // popping, so lets allow them all. NOTE: It assumes the data type is fundamentally
125 | // Plain Old Data (POD). TLDR: Serialise & Deserialise into/from a vector
126 |
127 | // Pushes any POD-like data into the message buffer
128 | template
129 | friend message& operator << (message& msg, const DataType& data)
130 | {
131 | // Check that the type of the data being pushed is trivially copyable
132 | static_assert(std::is_standard_layout::value, "Data is too complex to be pushed into vector");
133 |
134 | // Cache current size of vector, as this will be the point we insert the data
135 | size_t i = msg.body.size();
136 |
137 | // Resize the vector by the size of the data being pushed
138 | msg.body.resize(msg.body.size() + sizeof(DataType));
139 |
140 | // Physically copy the data into the newly allocated vector space
141 | std::memcpy(msg.body.data() + i, &data, sizeof(DataType));
142 |
143 | // Recalculate the message size
144 | msg.header.size = msg.size();
145 |
146 | // Return the target message so it can be "chained"
147 | return msg;
148 | }
149 |
150 | // Pulls any POD-like data form the message buffer
151 | template
152 | friend message& operator >> (message& msg, DataType& data)
153 | {
154 | // Check that the type of the data being pushed is trivially copyable
155 | static_assert(std::is_standard_layout::value, "Data is too complex to be pulled from vector");
156 |
157 | // Cache the location towards the end of the vector where the pulled data starts
158 | size_t i = msg.body.size() - sizeof(DataType);
159 |
160 | // Physically copy the data from the vector into the user variable
161 | std::memcpy(&data, msg.body.data() + i, sizeof(DataType));
162 |
163 | // Shrink the vector to remove read bytes, and reset end position
164 | msg.body.resize(i);
165 |
166 | // Recalculate the message size
167 | msg.header.size = msg.size();
168 |
169 | // Return the target message so it can be "chained"
170 | return msg;
171 | }
172 | };
173 |
174 |
175 | // An "owned" message is identical to a regular message, but it is associated with
176 | // a connection. On a server, the owner would be the client that sent the message,
177 | // on a client the owner would be the server.
178 |
179 | // Forward declare the connection
180 | template
181 | class connection;
182 |
183 | template
184 | struct owned_message
185 | {
186 | std::shared_ptr> remote = nullptr;
187 | message msg;
188 |
189 | // Again, a friendly string maker
190 | friend std::ostream& operator<<(std::ostream& os, const owned_message& msg)
191 | {
192 | os << msg.msg;
193 | return os;
194 | }
195 | };
196 |
197 |
198 | // Queue
199 | template
200 | class tsqueue
201 | {
202 | public:
203 | tsqueue() = default;
204 | tsqueue(const tsqueue&) = delete;
205 | virtual ~tsqueue() { clear(); }
206 |
207 | public:
208 | // Returns and maintains item at front of Queue
209 | const T& front()
210 | {
211 | std::scoped_lock lock(muxQueue);
212 | return deqQueue.front();
213 | }
214 |
215 | // Returns and maintains item at back of Queue
216 | const T& back()
217 | {
218 | std::scoped_lock lock(muxQueue);
219 | return deqQueue.back();
220 | }
221 |
222 | // Removes and returns item from front of Queue
223 | T pop_front()
224 | {
225 | std::scoped_lock lock(muxQueue);
226 | auto t = std::move(deqQueue.front());
227 | deqQueue.pop_front();
228 | return t;
229 | }
230 |
231 | // Removes and returns item from back of Queue
232 | T pop_back()
233 | {
234 | std::scoped_lock lock(muxQueue);
235 | auto t = std::move(deqQueue.back());
236 | deqQueue.pop_back();
237 | return t;
238 | }
239 |
240 | // Adds an item to back of Queue
241 | void push_back(const T& item)
242 | {
243 | std::scoped_lock lock(muxQueue);
244 | deqQueue.emplace_back(std::move(item));
245 |
246 | std::unique_lock ul(muxBlocking);
247 | cvBlocking.notify_one();
248 | }
249 |
250 | // Adds an item to front of Queue
251 | void push_front(const T& item)
252 | {
253 | std::scoped_lock lock(muxQueue);
254 | deqQueue.emplace_front(std::move(item));
255 |
256 | std::unique_lock ul(muxBlocking);
257 | cvBlocking.notify_one();
258 | }
259 |
260 | // Returns true if Queue has no items
261 | bool empty()
262 | {
263 | std::scoped_lock lock(muxQueue);
264 | return deqQueue.empty();
265 | }
266 |
267 | // Returns number of items in Queue
268 | size_t count()
269 | {
270 | std::scoped_lock lock(muxQueue);
271 | return deqQueue.size();
272 | }
273 |
274 | // Clears Queue
275 | void clear()
276 | {
277 | std::scoped_lock lock(muxQueue);
278 | deqQueue.clear();
279 | }
280 |
281 | void wait()
282 | {
283 | while (empty())
284 | {
285 | std::unique_lock ul(muxBlocking);
286 | cvBlocking.wait(ul);
287 | }
288 | }
289 |
290 | protected:
291 | std::mutex muxQueue;
292 | std::deque deqQueue;
293 | std::condition_variable cvBlocking;
294 | std::mutex muxBlocking;
295 | };
296 |
297 | // Connection
298 | // Forward declare
299 | template
300 | class server_interface;
301 |
302 | template
303 | class connection : public std::enable_shared_from_this>
304 | {
305 | public:
306 | // A connection is "owned" by either a server or a client, and its
307 | // behaviour is slightly different bewteen the two.
308 | enum class owner
309 | {
310 | server,
311 | client
312 | };
313 |
314 | public:
315 | // Constructor: Specify Owner, connect to context, transfer the socket
316 | // Provide reference to incoming message queue
317 | connection(owner parent, asio::io_context& asioContext, asio::ip::tcp::socket socket, tsqueue>& qIn)
318 | : m_asioContext(asioContext), m_socket(std::move(socket)), m_qMessagesIn(qIn)
319 | {
320 | m_nOwnerType = parent;
321 |
322 | // Construct validation check data
323 | if (m_nOwnerType == owner::server)
324 | {
325 | // Connection is Server -> Client, construct random data for the client
326 | // to transform and send back for validation
327 | m_nHandshakeOut = uint64_t(std::chrono::system_clock::now().time_since_epoch().count());
328 |
329 | // Pre-calculate the result for checking when the client responds
330 | m_nHandshakeCheck = scramble(m_nHandshakeOut);
331 | }
332 | else
333 | {
334 | // Connection is Client -> Server, so we have nothing to define,
335 | m_nHandshakeIn = 0;
336 | m_nHandshakeOut = 0;
337 | }
338 | }
339 |
340 | virtual ~connection()
341 | {}
342 |
343 | // This ID is used system wide - its how clients will understand other clients
344 | // exist across the whole system.
345 | uint32_t GetID() const
346 | {
347 | return id;
348 | }
349 |
350 | public:
351 | void ConnectToClient(olc::net::server_interface* server, uint32_t uid = 0)
352 | {
353 | if (m_nOwnerType == owner::server)
354 | {
355 | if (m_socket.is_open())
356 | {
357 | id = uid;
358 |
359 | // Was: ReadHeader();
360 |
361 | // A client has attempted to connect to the server, but we wish
362 | // the client to first validate itself, so first write out the
363 | // handshake data to be validated
364 | WriteValidation();
365 |
366 | // Next, issue a task to sit and wait asynchronously for precisely
367 | // the validation data sent back from the client
368 | ReadValidation(server);
369 | }
370 | }
371 | }
372 |
373 | void ConnectToServer(const asio::ip::tcp::resolver::results_type& endpoints)
374 | {
375 | // Only clients can connect to servers
376 | if (m_nOwnerType == owner::client)
377 | {
378 | // Request asio attempts to connect to an endpoint
379 | asio::async_connect(m_socket, endpoints,
380 | [this](std::error_code ec, asio::ip::tcp::endpoint endpoint)
381 | {
382 | if (!ec)
383 | {
384 | // Was: ReadHeader();
385 |
386 | // First thing server will do is send packet to be validated
387 | // so wait for that and respond
388 | ReadValidation();
389 | }
390 | });
391 | }
392 | }
393 |
394 |
395 | void Disconnect()
396 | {
397 | if (IsConnected())
398 | asio::post(m_asioContext, [this]() { m_socket.close(); });
399 | }
400 |
401 | bool IsConnected() const
402 | {
403 | return m_socket.is_open();
404 | }
405 |
406 | // Prime the connection to wait for incoming messages
407 | void StartListening()
408 | {
409 |
410 | }
411 |
412 | public:
413 | // ASYNC - Send a message, connections are one-to-one so no need to specifiy
414 | // the target, for a client, the target is the server and vice versa
415 | void Send(const message& msg)
416 | {
417 | asio::post(m_asioContext,
418 | [this, msg]()
419 | {
420 | // If the queue has a message in it, then we must
421 | // assume that it is in the process of asynchronously being written.
422 | // Either way add the message to the queue to be output. If no messages
423 | // were available to be written, then start the process of writing the
424 | // message at the front of the queue.
425 | bool bWritingMessage = !m_qMessagesOut.empty();
426 | m_qMessagesOut.push_back(msg);
427 | if (!bWritingMessage)
428 | {
429 | WriteHeader();
430 | }
431 | });
432 | }
433 |
434 |
435 |
436 | private:
437 | // ASYNC - Prime context to write a message header
438 | void WriteHeader()
439 | {
440 | // If this function is called, we know the outgoing message queue must have
441 | // at least one message to send. So allocate a transmission buffer to hold
442 | // the message, and issue the work - asio, send these bytes
443 | asio::async_write(m_socket, asio::buffer(&m_qMessagesOut.front().header, sizeof(message_header)),
444 | [this](std::error_code ec, std::size_t length)
445 | {
446 | // asio has now sent the bytes - if there was a problem
447 | // an error would be available...
448 | if (!ec)
449 | {
450 | // ... no error, so check if the message header just sent also
451 | // has a message body...
452 | if (m_qMessagesOut.front().body.size() > 0)
453 | {
454 | // ...it does, so issue the task to write the body bytes
455 | WriteBody();
456 | }
457 | else
458 | {
459 | // ...it didnt, so we are done with this message. Remove it from
460 | // the outgoing message queue
461 | m_qMessagesOut.pop_front();
462 |
463 | // If the queue is not empty, there are more messages to send, so
464 | // make this happen by issuing the task to send the next header.
465 | if (!m_qMessagesOut.empty())
466 | {
467 | WriteHeader();
468 | }
469 | }
470 | }
471 | else
472 | {
473 | // ...asio failed to write the message, we could analyse why but
474 | // for now simply assume the connection has died by closing the
475 | // socket. When a future attempt to write to this client fails due
476 | // to the closed socket, it will be tidied up.
477 | std::cout << "[" << id << "] Write Header Fail.\n";
478 | m_socket.close();
479 | }
480 | });
481 | }
482 |
483 | // ASYNC - Prime context to write a message body
484 | void WriteBody()
485 | {
486 | // If this function is called, a header has just been sent, and that header
487 | // indicated a body existed for this message. Fill a transmission buffer
488 | // with the body data, and send it!
489 | asio::async_write(m_socket, asio::buffer(m_qMessagesOut.front().body.data(), m_qMessagesOut.front().body.size()),
490 | [this](std::error_code ec, std::size_t length)
491 | {
492 | if (!ec)
493 | {
494 | // Sending was successful, so we are done with the message
495 | // and remove it from the queue
496 | m_qMessagesOut.pop_front();
497 |
498 | // If the queue still has messages in it, then issue the task to
499 | // send the next messages' header.
500 | if (!m_qMessagesOut.empty())
501 | {
502 | WriteHeader();
503 | }
504 | }
505 | else
506 | {
507 | // Sending failed, see WriteHeader() equivalent for description :P
508 | std::cout << "[" << id << "] Write Body Fail.\n";
509 | m_socket.close();
510 | }
511 | });
512 | }
513 |
514 | // ASYNC - Prime context ready to read a message header
515 | void ReadHeader()
516 | {
517 | // If this function is called, we are expecting asio to wait until it receives
518 | // enough bytes to form a header of a message. We know the headers are a fixed
519 | // size, so allocate a transmission buffer large enough to store it. In fact,
520 | // we will construct the message in a "temporary" message object as it's
521 | // convenient to work with.
522 | asio::async_read(m_socket, asio::buffer(&m_msgTemporaryIn.header, sizeof(message_header)),
523 | [this](std::error_code ec, std::size_t length)
524 | {
525 | if (!ec)
526 | {
527 | // A complete message header has been read, check if this message
528 | // has a body to follow...
529 | if (m_msgTemporaryIn.header.size > 0)
530 | {
531 | // ...it does, so allocate enough space in the messages' body
532 | // vector, and issue asio with the task to read the body.
533 | m_msgTemporaryIn.body.resize(m_msgTemporaryIn.header.size);
534 | ReadBody();
535 | }
536 | else
537 | {
538 | // it doesn't, so add this bodyless message to the connections
539 | // incoming message queue
540 | AddToIncomingMessageQueue();
541 | }
542 | }
543 | else
544 | {
545 | // Reading form the client went wrong, most likely a disconnect
546 | // has occurred. Close the socket and let the system tidy it up later.
547 | std::cout << "[" << id << "] Read Header Fail.\n";
548 | m_socket.close();
549 | }
550 | });
551 | }
552 |
553 | // ASYNC - Prime context ready to read a message body
554 | void ReadBody()
555 | {
556 | // If this function is called, a header has already been read, and that header
557 | // request we read a body, The space for that body has already been allocated
558 | // in the temporary message object, so just wait for the bytes to arrive...
559 | asio::async_read(m_socket, asio::buffer(m_msgTemporaryIn.body.data(), m_msgTemporaryIn.body.size()),
560 | [this](std::error_code ec, std::size_t length)
561 | {
562 | if (!ec)
563 | {
564 | // ...and they have! The message is now complete, so add
565 | // the whole message to incoming queue
566 | AddToIncomingMessageQueue();
567 | }
568 | else
569 | {
570 | // As above!
571 | std::cout << "[" << id << "] Read Body Fail.\n";
572 | m_socket.close();
573 | }
574 | });
575 | }
576 |
577 | // "Encrypt" data
578 | uint64_t scramble(uint64_t nInput)
579 | {
580 | uint64_t out = nInput ^ 0xDEADBEEFC0DECAFE;
581 | out = (out & 0xF0F0F0F0F0F0F0) >> 4 | (out & 0x0F0F0F0F0F0F0F) << 4;
582 | return out ^ 0xC0DEFACE12345678;
583 | }
584 |
585 | // ASYNC - Used by both client and server to write validation packet
586 | void WriteValidation()
587 | {
588 | asio::async_write(m_socket, asio::buffer(&m_nHandshakeOut, sizeof(uint64_t)),
589 | [this](std::error_code ec, std::size_t length)
590 | {
591 | if (!ec)
592 | {
593 | // Validation data sent, clients should sit and wait
594 | // for a response (or a closure)
595 | if (m_nOwnerType == owner::client)
596 | ReadHeader();
597 | }
598 | else
599 | {
600 | m_socket.close();
601 | }
602 | });
603 | }
604 |
605 | void ReadValidation(olc::net::server_interface* server = nullptr)
606 | {
607 | asio::async_read(m_socket, asio::buffer(&m_nHandshakeIn, sizeof(uint64_t)),
608 | [this, server](std::error_code ec, std::size_t length)
609 | {
610 | if (!ec)
611 | {
612 | if (m_nOwnerType == owner::server)
613 | {
614 | // Connection is a server, so check response from client
615 |
616 | // Compare sent data to actual solution
617 | if (m_nHandshakeIn == m_nHandshakeCheck)
618 | {
619 | // Client has provided valid solution, so allow it to connect properly
620 | std::cout << "Client Validated" << std::endl;
621 | server->OnClientValidated(this->shared_from_this());
622 |
623 | // Sit waiting to receive data now
624 | ReadHeader();
625 | }
626 | else
627 | {
628 | // Client gave incorrect data, so disconnect
629 | std::cout << "Client Disconnected (Fail Validation)" << std::endl;
630 | m_socket.close();
631 | }
632 | }
633 | else
634 | {
635 | // Connection is a client, so solve puzzle
636 | m_nHandshakeOut = scramble(m_nHandshakeIn);
637 |
638 | // Write the result
639 | WriteValidation();
640 | }
641 | }
642 | else
643 | {
644 | // Some biggerfailure occured
645 | std::cout << "Client Disconnected (ReadValidation)" << std::endl;
646 | m_socket.close();
647 | }
648 | });
649 | }
650 |
651 | // Once a full message is received, add it to the incoming queue
652 | void AddToIncomingMessageQueue()
653 | {
654 | // Shove it in queue, converting it to an "owned message", by initialising
655 | // with the a shared pointer from this connection object
656 | if(m_nOwnerType == owner::server)
657 | m_qMessagesIn.push_back({ this->shared_from_this(), m_msgTemporaryIn });
658 | else
659 | m_qMessagesIn.push_back({ nullptr, m_msgTemporaryIn });
660 |
661 | // We must now prime the asio context to receive the next message. It
662 | // wil just sit and wait for bytes to arrive, and the message construction
663 | // process repeats itself. Clever huh?
664 | ReadHeader();
665 | }
666 |
667 | protected:
668 | // Each connection has a unique socket to a remote
669 | asio::ip::tcp::socket m_socket;
670 |
671 | // This context is shared with the whole asio instance
672 | asio::io_context& m_asioContext;
673 |
674 | // This queue holds all messages to be sent to the remote side
675 | // of this connection
676 | tsqueue> m_qMessagesOut;
677 |
678 | // This references the incoming queue of the parent object
679 | tsqueue>& m_qMessagesIn;
680 |
681 | // Incoming messages are constructed asynchronously, so we will
682 | // store the part assembled message here, until it is ready
683 | message m_msgTemporaryIn;
684 |
685 | // The "owner" decides how some of the connection behaves
686 | owner m_nOwnerType = owner::server;
687 |
688 | // Handshake Validation
689 | uint64_t m_nHandshakeOut = 0;
690 | uint64_t m_nHandshakeIn = 0;
691 | uint64_t m_nHandshakeCheck = 0;
692 |
693 |
694 | bool m_bValidHandshake = false;
695 | bool m_bConnectionEstablished = false;
696 |
697 | uint32_t id = 0;
698 |
699 | };
700 |
701 | // Client
702 | template
703 | class client_interface
704 | {
705 | public:
706 | client_interface()
707 | {}
708 |
709 | virtual ~client_interface()
710 | {
711 | // If the client is destroyed, always try and disconnect from server
712 | Disconnect();
713 | }
714 |
715 | public:
716 | // Connect to server with hostname/ip-address and port
717 | bool Connect(const std::string& host, const uint16_t port)
718 | {
719 | try
720 | {
721 | // Resolve hostname/ip-address into tangiable physical address
722 | asio::ip::tcp::resolver resolver(m_context);
723 | asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port));
724 |
725 | // Create connection
726 | m_connection = std::make_unique>(connection::owner::client, m_context, asio::ip::tcp::socket(m_context), m_qMessagesIn);
727 |
728 | // Tell the connection object to connect to server
729 | m_connection->ConnectToServer(endpoints);
730 |
731 | // Start Context Thread
732 | thrContext = std::thread([this]() { m_context.run(); });
733 | }
734 | catch (std::exception& e)
735 | {
736 | std::cerr << "Client Exception: " << e.what() << "\n";
737 | return false;
738 | }
739 | return true;
740 | }
741 |
742 | // Disconnect from server
743 | void Disconnect()
744 | {
745 | // If connection exists, and it's connected then...
746 | if(IsConnected())
747 | {
748 | // ...disconnect from server gracefully
749 | m_connection->Disconnect();
750 | }
751 |
752 | // Either way, we're also done with the asio context...
753 | m_context.stop();
754 | // ...and its thread
755 | if (thrContext.joinable())
756 | thrContext.join();
757 |
758 | // Destroy the connection object
759 | m_connection.release();
760 | }
761 |
762 | // Check if client is actually connected to a server
763 | bool IsConnected()
764 | {
765 | if (m_connection)
766 | return m_connection->IsConnected();
767 | else
768 | return false;
769 | }
770 |
771 | public:
772 | // Send message to server
773 | void Send(const message& msg)
774 | {
775 | if (IsConnected())
776 | m_connection->Send(msg);
777 | }
778 |
779 | // Retrieve queue of messages from server
780 | tsqueue>& Incoming()
781 | {
782 | return m_qMessagesIn;
783 | }
784 |
785 | protected:
786 | // asio context handles the data transfer...
787 | asio::io_context m_context;
788 | // ...but needs a thread of its own to execute its work commands
789 | std::thread thrContext;
790 | // The client has a single instance of a "connection" object, which handles data transfer
791 | std::unique_ptr> m_connection;
792 |
793 | private:
794 | // This is the thread safe queue of incoming messages from server
795 | tsqueue> m_qMessagesIn;
796 | };
797 |
798 | // Server
799 | template
800 | class server_interface
801 | {
802 | public:
803 | // Create a server, ready to listen on specified port
804 | server_interface(uint16_t port)
805 | : m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port))
806 | {
807 |
808 | }
809 |
810 | virtual ~server_interface()
811 | {
812 | // May as well try and tidy up
813 | Stop();
814 | }
815 |
816 | // Starts the server!
817 | bool Start()
818 | {
819 | try
820 | {
821 | // Issue a task to the asio context - This is important
822 | // as it will prime the context with "work", and stop it
823 | // from exiting immediately. Since this is a server, we
824 | // want it primed ready to handle clients trying to
825 | // connect.
826 | WaitForClientConnection();
827 |
828 | // Launch the asio context in its own thread
829 | m_threadContext = std::thread([this]() { m_asioContext.run(); });
830 | }
831 | catch (std::exception& e)
832 | {
833 | // Something prohibited the server from listening
834 | std::cerr << "[SERVER] Exception: " << e.what() << "\n";
835 | return false;
836 | }
837 |
838 | std::cout << "[SERVER] Started!\n";
839 | return true;
840 | }
841 |
842 | // Stops the server!
843 | void Stop()
844 | {
845 | // Request the context to close
846 | m_asioContext.stop();
847 |
848 | // Tidy up the context thread
849 | if (m_threadContext.joinable()) m_threadContext.join();
850 |
851 | // Inform someone, anybody, if they care...
852 | std::cout << "[SERVER] Stopped!\n";
853 | }
854 |
855 | // ASYNC - Instruct asio to wait for connection
856 | void WaitForClientConnection()
857 | {
858 | // Prime context with an instruction to wait until a socket connects. This
859 | // is the purpose of an "acceptor" object. It will provide a unique socket
860 | // for each incoming connection attempt
861 | m_asioAcceptor.async_accept(
862 | [this](std::error_code ec, asio::ip::tcp::socket socket)
863 | {
864 | // Triggered by incoming connection request
865 | if (!ec)
866 | {
867 | // Display some useful(?) information
868 | std::cout << "[SERVER] New Connection: " << socket.remote_endpoint() << "\n";
869 |
870 | // Create a new connection to handle this client
871 | std::shared_ptr> newconn =
872 | std::make_shared>(connection::owner::server,
873 | m_asioContext, std::move(socket), m_qMessagesIn);
874 |
875 |
876 |
877 | // Give the user server a chance to deny connection
878 | if (OnClientConnect(newconn))
879 | {
880 | // Connection allowed, so add to container of new connections
881 | m_deqConnections.push_back(std::move(newconn));
882 |
883 | // And very important! Issue a task to the connection's
884 | // asio context to sit and wait for bytes to arrive!
885 | m_deqConnections.back()->ConnectToClient(this, nIDCounter++);
886 |
887 | std::cout << "[" << m_deqConnections.back()->GetID() << "] Connection Approved\n";
888 | }
889 | else
890 | {
891 | std::cout << "[-----] Connection Denied\n";
892 |
893 | // Connection will go out of scope with no pending tasks, so will
894 | // get destroyed automagically due to the wonder of smart pointers
895 | }
896 | }
897 | else
898 | {
899 | // Error has occurred during acceptance
900 | std::cout << "[SERVER] New Connection Error: " << ec.message() << "\n";
901 | }
902 |
903 | // Prime the asio context with more work - again simply wait for
904 | // another connection...
905 | WaitForClientConnection();
906 | });
907 | }
908 |
909 | // Send a message to a specific client
910 | void MessageClient(std::shared_ptr> client, const message& msg)
911 | {
912 | // Check client is legitimate...
913 | if (client && client->IsConnected())
914 | {
915 | // ...and post the message via the connection
916 | client->Send(msg);
917 | }
918 | else
919 | {
920 | // If we cant communicate with client then we may as
921 | // well remove the client - let the server know, it may
922 | // be tracking it somehow
923 | OnClientDisconnect(client);
924 |
925 | // Off you go now, bye bye!
926 | client.reset();
927 |
928 | // Then physically remove it from the container
929 | m_deqConnections.erase(
930 | std::remove(m_deqConnections.begin(), m_deqConnections.end(), client), m_deqConnections.end());
931 | }
932 | }
933 |
934 | // Send message to all clients
935 | void MessageAllClients(const message& msg, std::shared_ptr> pIgnoreClient = nullptr)
936 | {
937 | bool bInvalidClientExists = false;
938 |
939 | // Iterate through all clients in container
940 | for (auto& client : m_deqConnections)
941 | {
942 | // Check client is connected...
943 | if (client && client->IsConnected())
944 | {
945 | // ..it is!
946 | if(client != pIgnoreClient)
947 | client->Send(msg);
948 | }
949 | else
950 | {
951 | // The client couldnt be contacted, so assume it has
952 | // disconnected.
953 | OnClientDisconnect(client);
954 | client.reset();
955 |
956 | // Set this flag to then remove dead clients from container
957 | bInvalidClientExists = true;
958 | }
959 | }
960 |
961 | // Remove dead clients, all in one go - this way, we dont invalidate the
962 | // container as we iterated through it.
963 | if (bInvalidClientExists)
964 | m_deqConnections.erase(
965 | std::remove(m_deqConnections.begin(), m_deqConnections.end(), nullptr), m_deqConnections.end());
966 | }
967 |
968 | // Force server to respond to incoming messages
969 | void Update(size_t nMaxMessages = -1, bool bWait = false)
970 | {
971 | if (bWait) m_qMessagesIn.wait();
972 |
973 | // Process as many messages as you can up to the value
974 | // specified
975 | size_t nMessageCount = 0;
976 | while (nMessageCount < nMaxMessages && !m_qMessagesIn.empty())
977 | {
978 | // Grab the front message
979 | auto msg = m_qMessagesIn.pop_front();
980 |
981 | // Pass to message handler
982 | OnMessage(msg.remote, msg.msg);
983 |
984 | nMessageCount++;
985 | }
986 | }
987 |
988 | protected:
989 | // This server class should override thse functions to implement
990 | // customised functionality
991 |
992 | // Called when a client connects, you can veto the connection by returning false
993 | virtual bool OnClientConnect(std::shared_ptr> client)
994 | {
995 | return false;
996 | }
997 |
998 | // Called when a client appears to have disconnected
999 | virtual void OnClientDisconnect(std::shared_ptr> client)
1000 | {
1001 |
1002 | }
1003 |
1004 | // Called when a message arrives
1005 | virtual void OnMessage(std::shared_ptr> client, message& msg)
1006 | {
1007 |
1008 | }
1009 |
1010 | public:
1011 | // Called when a client is validated
1012 | virtual void OnClientValidated(std::shared_ptr> client)
1013 | {
1014 |
1015 | }
1016 |
1017 |
1018 | protected:
1019 | // Thread Safe Queue for incoming message packets
1020 | tsqueue> m_qMessagesIn;
1021 |
1022 | // Container of active validated connections
1023 | std::deque>> m_deqConnections;
1024 |
1025 | // Order of declaration is important - it is also the order of initialisation
1026 | asio::io_context m_asioContext;
1027 | std::thread m_threadContext;
1028 |
1029 | // These things need an asio context
1030 | asio::ip::tcp::acceptor m_asioAcceptor; // Handles new incoming connection attempts...
1031 |
1032 | // Clients will be identified in the "wider system" via an ID
1033 | uint32_t nIDCounter = 10000;
1034 | };
1035 | }
1036 | }
1037 |
1038 |
1039 |
--------------------------------------------------------------------------------
/extensions/olcPGEX_PopUpMenu.h:
--------------------------------------------------------------------------------
1 | /*
2 | olcPGEX_PopUp.h
3 |
4 | +-------------------------------------------------------------+
5 | | OneLoneCoder Pixel Game Engine Extension |
6 | | Retro PopUp Menu 1.0 |
7 | +-------------------------------------------------------------+
8 |
9 | What is this?
10 | ~~~~~~~~~~~~~
11 | This is an extension to the olcPixelGameEngine, which provides
12 | a quick and easy to use, flexible, skinnable context pop-up
13 | menu system.
14 |
15 | License (OLC-3)
16 | ~~~~~~~~~~~~~~~
17 |
18 | Copyright 2018 - 2020 OneLoneCoder.com
19 |
20 | Redistribution and use in source and binary forms, with or without
21 | modification, are permitted provided that the following conditions
22 | are met:
23 |
24 | 1. Redistributions or derivations of source code must retain the above
25 | copyright notice, this list of conditions and the following disclaimer.
26 |
27 | 2. Redistributions or derivative works in binary form must reproduce
28 | the above copyright notice. This list of conditions and the following
29 | disclaimer must be reproduced in the documentation and/or other
30 | materials provided with the distribution.
31 |
32 | 3. Neither the name of the copyright holder nor the names of its
33 | contributors may be used to endorse or promote products derived
34 | from this software without specific prior written permission.
35 |
36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
37 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
40 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 |
48 | Links
49 | ~~~~~
50 | YouTube: https://www.youtube.com/javidx9
51 | Discord: https://discord.gg/WhwHUMV
52 | Twitter: https://www.twitter.com/javidx9
53 | Twitch: https://www.twitch.tv/javidx9
54 | GitHub: https://www.github.com/onelonecoder
55 | Homepage: https://www.onelonecoder.com
56 |
57 | Author
58 | ~~~~~~
59 | David Barr, aka javidx9, ©OneLoneCoder 2019, 2020
60 | */
61 |
62 |
63 | /*
64 | Example
65 | ~~~~~~~
66 |
67 | #define OLC_PGEX_POPUPMENU
68 | #include "olcPGEX_PopUpMenu.h"
69 |
70 | NOTE: Requires a 9-patch sprite, by default each patch is
71 | 8x8 pixels, patches are as follows:
72 |
73 | | PANEL TL | PANEL T | PANEL TR | SCROLL UP | CURSOR TL | CURSOR TR |
74 | | PANEL L | PANEL M | PANEL R | SUBMENU | CURSOR BL | CURSOR BR |
75 | | PANEL BL | PANEL B | PANEL BR | SCROLL DOWN | UNUSED | UNUSED |
76 |
77 | You can find an example sprite here:
78 | https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Videos/RetroMenu.png
79 |
80 | Constructing A Menu
81 | ~~~~~~~~~~~~~~~~~~~
82 |
83 | // Declaration (presumably inside class)
84 | olc::popup::Menu m;
85 |
86 | // Construction (root menu is a 1x5 table)
87 | m.SetTable(1, 5);
88 |
89 | // Add first item to root menu (A 1x5 submenu)
90 | m["Menu1"].SetTable(1, 5);
91 |
92 | // Add items to first item
93 | m["Menu1"]["Item1"];
94 | m["Menu1"]["Item2"];
95 |
96 | // Add a 4x3 submenu
97 | m["Menu1"]["Item3"].SetTable(4, 3);
98 | m["Menu1"]["Item3"]["Option1"];
99 | m["Menu1"]["Item3"]["Option2"];
100 |
101 | // Set properties of specific item
102 | m["Menu1"]["Item3"]["Option3"].Enable(false);
103 | m["Menu1"]["Item3"]["Option4"];
104 | m["Menu1"]["Item3"]["Option5"];
105 | m["Menu1"]["Item4"];
106 |
107 | // Add second item to root menu
108 | m["Menu2"].SetTable(3, 3);
109 | m["Menu2"]["Item1"];
110 | m["Menu2"]["Item2"].SetID(1001).Enable(true);
111 | m["Menu2"]["Item3"];
112 |
113 | // Construct the menu structure
114 | m.Build();
115 |
116 |
117 | Displaying a Menu
118 | ~~~~~~~~~~~~~~~~~
119 |
120 | // Declaration of menu manager (presumably inside class)
121 | olc::popup::Manager man;
122 |
123 | // Set the Menu object to the MenuManager (call once per pop)
124 | man.Open(&m);
125 |
126 | // Draw Menu at position (30, 30), using "patch sprite"
127 | man.Draw(sprGFX, { 30,30 });
128 |
129 |
130 | Interacting with menu
131 | ~~~~~~~~~~~~~~~~~~~~~
132 |
133 | // Send key events to menu
134 | if (GetKey(olc::Key::UP).bPressed) man.OnUp();
135 | if (GetKey(olc::Key::DOWN).bPressed) man.OnDown();
136 | if (GetKey(olc::Key::LEFT).bPressed) man.OnLeft();
137 | if (GetKey(olc::Key::RIGHT).bPressed) man.OnRight();
138 | if (GetKey(olc::Key::Z).bPressed) man.OnBack();
139 |
140 | // "Confirm/Action" Key does something, if it returns non-null
141 | // then a menu item has been selected. The specific item will
142 | // be returned
143 | olc::popup::Menu* command = nullptr;
144 | if (GetKey(olc::Key::SPACE).bPressed) command = man.OnConfirm();
145 | if (command != nullptr)
146 | {
147 | std::string sLastAction =
148 | "Selected: " + command->GetName() +
149 | " ID: " + std::to_string(command->GetID());
150 |
151 | // Optionally close menu?
152 | man.Close();
153 | }
154 |
155 | */
156 |
157 | #ifndef OLC_PGEX_POPUPMENU_H
158 | #define OLC_PGEX_POPUPMENU_H
159 |
160 | #include
161 |
162 | #include "olcPixelGameEngine.h"
163 |
164 | namespace olc
165 | {
166 | namespace popup
167 | {
168 | constexpr int32_t nPatch = 8;
169 |
170 | class Menu
171 | {
172 | public:
173 | Menu();
174 | Menu(const std::string n);
175 |
176 | Menu& SetTable(int32_t nColumns, int32_t nRows);
177 | Menu& SetID(int32_t id);
178 | Menu& Enable(bool b);
179 |
180 | int32_t GetID();
181 | std::string& GetName();
182 | bool Enabled();
183 | bool HasChildren();
184 | olc::vi2d GetSize();
185 | olc::vi2d& GetCursorPosition();
186 | Menu& operator[](const std::string& name);
187 | void Build();
188 | void DrawSelf(olc::PixelGameEngine& pge, olc::Sprite* sprGFX, olc::vi2d vScreenOffset);
189 | void ClampCursor();
190 | void OnUp();
191 | void OnDown();
192 | void OnLeft();
193 | void OnRight();
194 | Menu* OnConfirm();
195 | Menu* GetSelectedItem();
196 |
197 | protected:
198 | int32_t nID = -1;
199 | olc::vi2d vCellTable = { 1, 0 };
200 | std::unordered_map itemPointer;
201 | std::vector items;
202 | olc::vi2d vSizeInPatches = { 0, 0 };
203 | olc::vi2d vCellSize = { 0, 0 };
204 | olc::vi2d vCellPadding = { 2, 0 };
205 | olc::vi2d vCellCursor = { 0, 0 };
206 | int32_t nCursorItem = 0;
207 | int32_t nTopVisibleRow = 0;
208 | int32_t nTotalRows = 0;
209 | const olc::vi2d vPatchSize = { nPatch, nPatch };
210 | std::string sName;
211 | olc::vi2d vCursorPos = { 0, 0 };
212 | bool bEnabled = true;
213 | };
214 |
215 | class Manager : public olc::PGEX
216 | {
217 | public:
218 | Manager();
219 | void Open(Menu* mo);
220 | void Close();
221 | void OnUp();
222 | void OnDown();
223 | void OnLeft();
224 | void OnRight();
225 | void OnBack();
226 | Menu* OnConfirm();
227 | void Draw(olc::Sprite* sprGFX, olc::vi2d vScreenOffset);
228 |
229 | private:
230 | std::list