Jump to content

Drew_Benton

Developers
  • Posts

    27
  • Joined

  • Last visited

Everything posted by Drew_Benton

  1. On the topic of next steps with performance and how far UltraEngine has come since Leadwerks, now that there is an ECS system, I wanted to ask you @Josh if you have ever checked into Unity DOTS? I first started reading about it a year ago or so, and it seemed like an interesting implementation of ECS. The TL;DR as I understand it, is basically you hand craft your component layouts to be cache friendly, allocate raw arrays of component data, process them in a hardware friendly way, then reap crazy performance benefits over the traditionally OOP design that people use to implement ECS that results in memory being scattered about (and thus high cache misses). What I'm most uncertain about is how to actually apply that in practice to a real game as opposed a tech demo. This video was by far the most comprehensive in the details of data layout optimizations and its impact on ECS: I was a bit skeptical about real world results, but I came across a demo I was able to do some minor updates to test locally in Unity with, and sure enough, it was pretty legit: Finally, an interesting larger scale crowd simulation demo someone made: I wonder if anything like this would further help UltraEngine with even more scale?
  2. I'm on Windows 10, and the WM_PAINT did happen on invalidation when clicking in the middle of the thin border between windows, but just for the parent window and not the child window. I had some code to PeekMessage and check what was going on before UltraEngine processed the events, so that's how I arrived at the conclusion to not check the window source. I can't attach a MP4 file, but here's an example: https://gyazo.com/71f17118ad6d81f388b0f748257570cc I wonder if making the main window the render window, and a second window the UI window would result in the intended behavior without additional modifications?
  3. @reepblue's code seems to work great, because it avoids trying to solve the issue of "when to re-render". I think this is the preferred way to go about an event driven system like this on Windows. I feel the Example 3 code is missing a lot of common render invalidation events (conceptually speaking), which are causing the issues you are seeing. I ran the example and can reproduce the exact issues you are having, so I think it's code related and not hardware/driver related. For reference, I have a RTX 3080 TI, also using 527.56 drivers running 2x 4k monitors. For example, on WIndows, I feel you should dirty the render view to cause a redraw when: The app is moved The app is resized When the parent window repaints Consider the following modified Example 3: #include "UltraEngine.h" using namespace UltraEngine; const int SidePanelWidth = 200; const int Indent = 8; // Callback function for resizing the viewport bool ResizeViewport(const Event& ev, shared_ptr<Object> extra) { // If the window resize event is captured auto window = ev.source->As<Window>(); // Get the new size of the applications window iVec2 sz = window->ClientSize(); auto viewport = extra->As<Window>(); // Set the position and size of the viewport window viewport->SetShape(SidePanelWidth, Indent, sz.x - SidePanelWidth - Indent, sz.y - Indent * 2); return true; } //Custom event ID const EventId EVENT_VIEWPORTRENDER = EventId(101); int main(int argc, const char* argv[]) { // Disable asynchronous rendering so window resizing will work with 3D graphics AsyncRender(false); // Get the available displays auto displays = GetDisplays(); // Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR | WINDOW_RESIZABLE); // Create user interface auto ui = CreateInterface(window); // Get the size of the user interface iVec2 sz = ui->background->ClientSize(); // Create a treeview widget auto treeview = CreateTreeView(Indent, Indent, SidePanelWidth - Indent * 2, sz.y - Indent * 2, ui->root); // Anchor left, top and bottom of treeview widget treeview->SetLayout(1, 0, 1, 1); // Add nodes to the treeview widget treeview->root->AddNode("Object 1"); treeview->root->AddNode("Object 2"); treeview->root->AddNode("Object 3"); // Create a viewport window auto viewport = CreateWindow("", SidePanelWidth, Indent, sz.x - SidePanelWidth - Indent, sz.y - Indent * 2, window, WINDOW_CHILD); // Adjust the size of the viewport when the applications window is resized (this will callback to our ResizeViewport() function) ListenEvent(EVENT_WINDOWSIZE, window, ResizeViewport, viewport); // Create a framebuffer auto framebuffer = CreateFramebuffer(viewport); // Create a world auto world = CreateWorld(); // Create a camera auto camera = CreateCamera(world); camera->SetClearColor(0.125); camera->SetPosition(0, 0, -4); // Create a light auto light = CreateBoxLight(world); light->SetRotation(35, 45, 0); light->SetRange(-10, 10); // Create a model auto model = CreateSphere(world); model->SetColor(0, 0, 1); // This varialble will be used for viewport refreshing bool dirty = false; // Main loop while (true) { // Wait for event const Event ev = WaitEvent(); // Evaluate event switch (ev.id) { case EVENT_WINDOWMOVE: Print("Window move"); if (not dirty) { dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); Print("viewport refresh"); } break; case EVENT_WINDOWSIZE: Print("Window size"); if (not dirty) { dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); Print("viewport refresh"); } break; //Close window when escape key is pressed case EVENT_KEYDOWN: if (ev.source == window and ev.data == KEY_ESCAPE) return 0; break; case EVENT_WINDOWCLOSE: if (ev.source == window) return 0; break; case EVENT_WINDOWPAINT: //if (ev.source == viewport) { Print("Window paint"); if (not dirty) { // This prevents excessive paint events from building up, especially during window resizing // This event is added to the end of the event queue, so if a lot of paint events build up, it will // only cause a single render to be performed. dirty = true; EmitEvent(EVENT_VIEWPORTRENDER, viewport); Print("viewport refresh"); } } break; case EVENT_VIEWPORTRENDER: world->Render(framebuffer); dirty = false; Print("Viewport render"); break; } } return 0; } This should solve the recent issues you've posted about because: If you move the Windows offscreen and back on, that counts as a move, which triggers a render, ensuring the viewport doesn't stay invalidated from being offscreen. When you resize the window, the render view gets invalided as expected during the resize itself, but it will redraw once you complete the resize. When you click in the empty space between the two windows, the parent window gets a paint message in such a way the child is not currently invalidated, but should be, which is why the viewport disappears. That behavior is fixed by not checking the message source in the code for the paint message, which should also fix other invalidation issues stemming from the parent. On one hand, maybe the engine could get some event processing changes to specifically address some of these issues, but from my experiences trying to track down and understand obscure Windows behaviors when it comes to events is usually not worth it. I just think it's far easier to model your code like reepblue did, and just avoid most of those issues in the first place by always updating/rendering. The other alternative is to possibly render selectively to an image file, and then use simpler, but more comprehensive Win32 message processing to ensure only the minimal amount of redrawing happens outside of a rendering context where you can manage the HDC of a single HWND and not deal with multiple windows and different behaviors of messages across a parent/child setup. Doesn't sound like that's what you're after here though, but if you wanted to minimize 3d rendering due to needing to limit graphics resource usage, I'd consider something like that maybe.
  4. You're right. Upon digging even more and deleting various cached files and removing extra source code, the real problem was that my 'ComponentSystem.h' was generated once, but didn't get regenerated again by the pre-processor: // This file is generated by the pre-processor. Do not modify it. // Generated on: Wed Jan 18 00:48:19 2023 However, my 'ComponentSystem.cpp' file did: // This file is generated by the pre-processor. Do not modify it. // Generated on: Wed Jan 18 10:31:21 2023 I realized this was the actual problem when I was getting compile errors for code that wasn't even in the project anymore. Upon deleting 'ComponentSystem.h' and getting it re-generated, the error now goes away.
  5. I found the exact, replicable problem as I was zipping it up to attach for you. At one point, I wanted to switch between a few of the different examples, so I renamed "main.cpp" to "main2.cpp" and excluded it from my Solution. I then added a new "main.cpp", but I just now noticed Visual Studio put it in the root folder by default, instead of the "Source" folder. That was what was causing the problem, because as soon as I move "main.cpp" back into the "Source" folder, the issue goes away. Moving it back one level above "Source" triggers the same error again. Mystery solved, I'm still getting used to the new required project layouts and whatnot, so I'll have to remember to keep track of where new files get put!
  6. Thank you for looking into this so quickly. I updated and tried again, but still had the same issue, so I tried what SpiderPig suggested: A fresh project seems to fix the issue. I know just how finicky C++ can get when code generation is involved (noticed components need to be in the components folder for the preprocessor), so as I continue to experiment, I'll try this first from now on if I run into the problem again. Thanks!
  7. UltraEngine Version: 1.0.1 Adding a CameraControls component to the Camera now seems to cause a heap corruption error on exit. The code I'm using is simply: auto actor = CreateActor(camera); actor->AddComponent<CameraControls>(); For example: #include "UltraEngine.h" #include "ComponentSystem.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR); //Create a framebuffer auto framebuffer = CreateFramebuffer(window); //Create a world auto world = CreateWorld(); //Create a camera auto camera = CreateCamera(world); camera->SetClearColor(0.125); camera->SetFov(70); camera->Move(0, 2, -8); auto actor = CreateActor(camera); actor->AddComponent<CameraControls>(); //Create light auto light = CreateBoxLight(world); light->SetRotation(45, 35, 0); light->SetRange(-10, 10); //Load FreeImage plugin auto plugin = LoadPlugin("Plugins/FITextureLoader"); //Model by PixelMannen //https://opengameart.org/content/fox-and-shiba auto model = LoadModel(world, "https://github.com/UltraEngine/Documentation/raw/master/Assets/Models/Characters/Fox.glb"); model->SetScale(0.05); model->Animate(1); model->SetRotation(0, -90, 0); auto neck = model->skeleton->FindBone("b_Neck_04"); Vec3 rotation; //Main loop while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { world->Update(); rotation.y = Cos(float(Millisecs()) / 10.0f) * 65.0f; neck->SetRotation(rotation); world->Render(framebuffer); } return 0; } This example, which was working in the YT video, now also exhibits this same issue: https://www.ultraengine.com/learn/Terrain_SetMaterial?lang=cpp Message: Stack Trace: ucrtbased.dll!00007ffd7e4fce3d() Unknown ucrtbased.dll!00007ffd7e500275() Unknown > Ultra1_d.exe!operator delete(void * block) Line 38 C++ Ultra1_d.exe!operator delete(void * block, unsigned __int64 __formal) Line 32 C++ Ultra1_d.exe!std::_Ref_count_obj2<Actor>::`scalar deleting destructor'(unsigned int) C++ Ultra1_d.exe!std::_Ref_count_obj2<Actor>::_Delete_this() Line 2053 C++ Ultra1_d.exe!std::_Ref_count_base::_Decwref() Line 1119 C++ Ultra1_d.exe!std::_Ptr_base<UltraEngine::ActorBase>::_Decwref() Line 1399 C++ Ultra1_d.exe!std::weak_ptr<UltraEngine::ActorBase>::~weak_ptr<UltraEngine::ActorBase>() Line 2996 C++ Ultra1_d.exe!UltraEngine::Entity::~Entity(void) Unknown Ultra1_d.exe!UltraEngine::Camera::~Camera(void) Unknown Ultra1_d.exe!UltraEngine::Camera::`vector deleting destructor'(unsigned int) Unknown Ultra1_d.exe!std::_Destroy_in_place<class UltraEngine::Camera>(class UltraEngine::Camera &) Unknown Ultra1_d.exe!std::_Ref_count_obj2<class UltraEngine::Camera>::_Destroy(void) Unknown Ultra1_d.exe!std::_Ref_count_base::_Decref() Line 1111 C++ Ultra1_d.exe!std::_Ptr_base<UltraEngine::Camera>::_Decref() Line 1337 C++ Ultra1_d.exe!std::shared_ptr<UltraEngine::Camera>::~shared_ptr<UltraEngine::Camera>() Line 1620 C++ Ultra1_d.exe!main(int argc, const char * * argv) Line 57 C++ Ultra1_d.exe!invoke_main() Line 79 C++ Ultra1_d.exe!__scrt_common_main_seh() Line 288 C++ Ultra1_d.exe!__scrt_common_main() Line 331 C++ Ultra1_d.exe!mainCRTStartup(void * __formal) Line 17 C++ kernel32.dll!00007ffeb3b87614() Unknown ntdll.dll!00007ffeb52226a1() Unknown Thanks!
  8. Congrats on this next impressive milestone Josh! It's really respectable just how far you've come since the days of the BlitzMax Leadwerks version, and just how much time you've dedicated to this project. Checking my email, I first bought Leadwerks Jan 3, 2009, and that seems like a lifetime ago now. I grabbed a yearly sub to UltraEngine to continue supporting the dream. Not sure if this year will finally be the year I seriously pursue a hobbyist gamedev project or not, but I enjoyed watching the "Introduction to Ultra Engine API" video on YT, and I still love the pragmatic way your API works, which was what got me into the original version to begin with. Anyways, keep up the great work. I look forward to an exciting year of further updates and awesome features!
  9. The general rule of thumb is to never copy any user/3rd party header files/libs into the base compiler paths. Instead, you just want to place it (the complete enet package) in a stable path that won't be changing, for example, "C:\dev". Once you have it in a stable path, you can: 1. Click Tools->Options 2. Projects and Settings->VC++ Directories. 3. Choose "Library files" and add the directory to the end of the list that contains the library files. 4. Choose "Include files" and add the directory to the end of the list that contains the base paths to the header files. Now, you can #include<enet.h> and it will search the registered directories first and find it. Likewise, when the linker goes to search its paths for the library you added in the Additional Dependencies, it'll go there first. See: what is the difference between #include <filename> and #include “filename” as well. This method is for global libraries though, things you set once and want to reuse for many projects. You maintain only 1 version of the library. If you only want to setup the current project only, what you do instead is: 1. Click Project->Properties 2. Choose Configuration Properties->C/C++ 3. Click the right most "..." button inside the "Additional Include Directories" field. 4. Add the directory to the end of the list that contains the base path for the header files 5. Choose Configuration Properties->Linker 6. Click the right most "..." button inside the "Additional Library Directories" field. 7. Add the directory to the end of the list that contains the library files. 8. Repeat for each "Configuration" (Debug/Release for example) Now, only the current project is setup to look for the library from a common path. IMPORTANT NOTE: Depending on how the API is setup determines which header folder you add. For example, it is advised to use the 'include' folder as the base header path (#include <enet/enet.h>) rather than using the 'enet' folder as the base path (#include<enet>). The reason why you usually take the first approach is to avoid header file name conflicts. The global approach is good for existing libraries that are common dependencies among many projects. The local approach is good for when you have different versions of the same library that you need to use in different projects. For example, there are many versions of ZLIB. If you need to use specific versions across different projects, then you have to set the paths for each library individually. If you were only using one version of ZLIB for all projects, then you can just setup the global paths instead. One last important note. Maintaining dependencies can be very tricky if you are not careful. For example, let's say you use the global approach. You migrate computers in the future and forget to save the the exact version you were using. Or, you send the code to someone who does not have the same dependency in the path you did. All sorts of coding headaches can arise as a result of this. I myself would rather keep all dependencies in a workspace folder for the projects I am using it with, so when I backup the project or send it to someone, everything they need is right there. Also, if you go and change anything in the global path, you will need to recompile all projects that depended on it and retest them to make sure they do not break. I've seen a lot of projects get ruined from simple things like this and I've made the mistake one too many times! Setting up a personal source code repository can help with the matter as well.
  10. The only 3rd party tool out there that could do it is Ultimate Unwrap 3D. UU3D is a must have tool, even outside of Leadwerks, but assuming you had that, you'd just download the "Leadwerks Engine (GMF)" plugin from their 3rd Party Plugins page. Then inside UU3D, you'd import your FBX (File->Open) then save it as the GMF file (File->Save As). Leadwerks requires DDS textures, so you'd have to make sure you account for that. There might be other limitations in regards to animations or bones or what have you, so you'd need to check out the resources on content creation on the site as well.
  11. Just for the sake of writing, here's my computer story. Also, I'm not QQ'ing about anything, it's just the timing of it (weekend) really sucks since I had some stuff I was planning on doing. Anyways though - Saturday May 15th, I ordered a 2TB backup drive and a sata/ide to usb adapter. I figured I'd go ahead and start backing up all my stuff during the week because one of my hard drives (750gb Seagate Barracuda) had been reporting errors (spin retry count) for over a year now. During that time, I just stopped using the drive and left all the data intact, copying all but a couple of large media folders to my other 750gb HD (I had bought 3 a while ago). From Monday to Friday, I spent all day every day working on backing up my data. I have about 5 hard drives laying around with stuff on them from over the years, so I had to consolidate it all first onto one drive. From there, I had to duplicate that across the 2TB drive and then to my third 750gb drive. The biggest problem I had when moving all that data was lockups from Teracopy and then antivirus interruptions from AVG. All in all, it took all week to backup everything, but I finally got it done and all my extra hard drives formatted. Ideally, I need to random fill wipe them all, but I don't have the hardware for that since I only have one adapter and it takes a very long time to do drives that large. So, after all the software backups were completed, it was time to remove hard drives from my system. I had left my case open on the floor with a few fans blowing across it and the extra hard drives I had attached internally but were laying outside the case due to space constraints. I've worked with computers a while, so I know how careful you have to be with them. After I took out all the hard drives I had to boot back up a couple of different times to ensure I had labeled them right, marking the bad one, the one that's going to be my new storage, and then the previous storage that I'm saving with the 2TB. Along that way, Windows notified me my system configuration had changed too much and I'd need to activate again. Bummer. It's a genuine Windows copy, but I got it from MSDNAA while I was in college, so sometimes those can be quirky on reactivates. Anyways, I finally had everything working fine so I needed to put in my final HD and then close up the case. Somewhere along the way, I must have bumped a cord or something and didn't notice because as soon as I started up the PC made a really scary beep, Since it was Friday morning like 3am, I cut the power so it'd not wake up everyone else in the house. I had to wait about 3-4 more hours before I could get back to working on it. After the time passed, I booted up the PC again to try and figure out what happened. It just powered on for a couple of seconds, then powered off. It did this over and over until I cut the power. I figured I had a short somewhere since I've seen computers do similar things before. I took it all apart and cleaned everything out. While it was totally disassembled on my floor, I tried booting it up again. Success! Carefully, I put it all back together back into the case and started it up again. Success! By now I was feeling pretty good since I'd not have to replace anything. However, when I got into Windows, the sound wasn't working. I have one of those asus xonar cards that requires a power cord so I jiggled that in the case and that seemed to fix it. I had only a few wires hanging out of the case still since I was taking very small steps in putting it back together. I didn't want anything to happen again. As I put in the last two cables and slide the case over, it happened again. The PC shut off. !@%!#^@#^!#$ It started doing the same thing with the endless reboots. Annoyed, I took it all apart again to try and figure out what happened. This time though, it never made it out of the endless reboot cycle. I'm not sure what happened, but I'm thinking the power cord on my sound card and the 1x PCI-E slot it fits in is to blame. Maybe it got loose or out of place a bit on the slightest movement and as a result, caused a short in the mobo. I tried booting up using only a CPU and PSU but it was still stuck in the endless cycle. I tried removing the battery as well and was going to clear the CMOS but at that point I figured it was a lost cause. I read up online about the model I have (x38-ds4) and the "endless reboot" problem it seems to get. I figure that was triggered somehow and it wasn't an electrical short that brought down my PC. I haven't done any PC maintenance in years because I know computers get "settled" and as soon as you try to do something with them, they have a tendency to break. That's why I don't even mess with computers or upgrade them as much anymore as I used to, it's just too iffy. Anyways though, so now I'm looking at what my options are. I've been waiting since 2009 to upgrade my PC, but I've not yet found an upgrade that would give a good price/performance ratio. I mean I have an xeon X3350, which is almost literally the same as a Q9540 (the xenon was a lot cheaper when I bought it, but it still cost a lot). So it's a powerful little CPU, but it doesn't have HT. That means if I were to upgrade, I'd only get noticeable performance increases if I went with a i7-920 or above CPU. Anything else is simply not worth it. Therein lies the problem. Why spend another 1k+ for a new i7-920 computer when I'm not really going to be able to make the most use of it? The same is true of upgrading the graphics card, what am I going to use a 1gb 5870 or gtx 480 for? So, I've put off upgrading or parts buying for almost 2 years now because I'm "fine" with what I have. My 8800gt is like 4-5 generations old now but it still works fine. If I am going to upgrade, it needs to be a significant upgrade that justifies buying new stuff to last another number of years. For a power user like me, I look at it like this - each 1k that is spent means the computer should last at least 1 year. So if I put down ~2k for a PC, it should last at least 2 years for me and then still be usable by others with less demanding needs. I pass all my old computers down through the family so they all end up paying for themselves. Enter the 980x. 6-core cpu with HT, now there's an upgrade that would be significant enough to make. However, $1000 for the CPU alone? No way. I just can't justify spending that much money on something that is going to be replaced in like a year. It'll still have value for many years to come, but it'd never pay for itself if I got it. So, scratch that. Instead, I'll just wait for the consumer level 6 core w/ HT cpus which should be coming out in the 3Q (i7-970 supposedly). Now I'm not that much of a waiter, because if you just wait for the next best thing, you'll never get to enjoy anything as it all passes you by. However, if I can get by just fine with what I have, then I don't mind waiting to upgrade to something that really seems like it'd be worth the investment. Where does that leave me in regards to my current PC? 1. Pay ~100 for a replacement mobo to get a working system back up. Assuming everything else works still fine, then I can get back to how things were and continue to wait to upgrade. However, I've been itching to do some upgrading for a while now so that brings me to 2. 2. Upgrade CPU, Ram, Mobo, and Video Card (1-2 generations old, not latest) and reuse HDDs, Case, PSU. This is more cost efficient in the long run since I save in rebuying those parts, and this computer is not the type to be handed down anyways due to how big it is. 3. Wait it out for the next big upgrade. I'll make use of my desktop replacement laptop (which is what I'm on now) to get the most out of a poor investment. I was thinking at the time, it'd be a good idea to get it because of what I thought I needed, but it turned out to be a bad decision. It's not a bad product, it's just not what I needed: HP Pavilion HDX9494NR 4. Downgrade. Make use of as much as I can to put together a lesser computer. The next paragraph explains this a bit more. I still have a C2D 8400 cpu laying around unused that I could make use of as well. I have enough parts to build two smaller PCs actually come to think about it. I'd just need mobos and cases and another PSU really. There are some other factors to consider as well. Right now, my desktop PC warms my room by at least 2-3 degrees Fahrenheit. That on top of the fact it's about to be summer time here in Texas, that is going to be bothersome. It's also rather noisy and large and uses a lot of power. It's so large and heavy in fact that I've been wanting to scale down significantly for a while now. I'm thinking about buying a more specialized case and changing out a few things to make it more efficient and less "polluting" to my room. That's why I'm in no rush to buy a mobo to get it back working; I want to think of a longer term strategy to take while the opportunity presents itself. Monetary issues are not a constraint right now and I do have time to think it out now, since it's the weekend. Of course, I'm not asking anyone for advice on what to do, I'm just thinking aloud for my own edification. I like writing That's basically where I am right now. I'm just thinking aloud to help wake up and start thinking of the possibilities ahead. I guess at least I have my backups done so I'm not preoccupied at the thought of having lost anything. Also since I'm not employed right now, I don't have the need to act asap and can take some time to think things through. Maybe it was a good thing after all with the timing. I'll probably come back to this post after I've done some more research and made a decision. For now though, I need to start working on a project that I was originally intending to spend all of last week on. Dang, I don't like getting behind.
  12. Drew_Benton

    May 20th, 2010

    I guess it's been a while since I added a blog entry. Since I don't have anything specific to write about, I've stuck with a generic boring title. I spent all week working on backing up the past 5 years of my computer life to an external hard drive (WD Elements) as well as another internal one. One of my hard drives has been having some problems lately and I've ignored it for over a year. I decided to stop chancing it and simply put in the time to save all my stuff that I've worked on and accumulated over the past 5 years. It was really time consuming, but now I can work in peace without the worry of losing my stuff. To help make copying easier, I used Teracopy. It's an excellent little utility to help manage larger file copies. I did run into some problems with it since I was moving hundreds of GB and 100k's of files at a time, but it was manageable. Most of the problems I think I had weren't with the program alone as much as with Windows and the hard drives themselves. Who knows though, at least the task is done now! Last week, I decided to give some new technology a try. I got a Zotac Mag to give a whirl. My ideal use for it is to run as a low power, noise, heat server for some of my future programs. So far, I'm loving it! I first put on XP and it all went really nice. However, I didn't need XP for the server stuff so I put on Sever 2003 instead. I'm very impressed with it. It "feels" fast and while I know a lot of that has to do with not having that much stuff installed on it, it still seems to be able to handle a lot of things I'd thought it'd struggle with. It does have support for 3D hardware accelerated graphics via the Nvidia Ion chipset, which makes it far more useful to me than any of the intel integrated stuff that can't really do much. In fact, I can ran Leadwerks just fine on it although the framerate is around the 20s in the editor most of the time, but it's still very responsive and usable. I am very satisfied with the purchase and will be looking to see how I can fit such machines into my deployment strategies if I ever get something to "ship". The only thing I am not happy about is the cost of laptop memory. It requires that style of DDR 2 800 and that would set me back another $100 for 2 x 2GB sticks. I figure it's not worth paying 30% more of the price for the resulting performance increase, which won't be that much currently. Eventually though, I'd add more if I had the need. I even thought about throwing in a SSD, but that too won't even give a decent ROI since it's just a light weight server that won't be even using the HD that much. In other exciting news, NetDog 2.X.X is here! I didn't get too far into 1.7.1, but it looks like the new version 2.X.X is going to help make development a lot more fun and exciting. I'll probably start looking into it soon to get down the changes and reevaluate some of my current projects I wanted to use it in to see how much more they could benefit with it as opposed to trying to do it myself. I really want to get a working prototype done sometime in the next few months, but I don't think I can focus on that long enough to finish. I still need to go back and finish my cegui tool as well. I'm not in any rush though because I still don't want to lock myself into one version of Leadwerks with 2.32 around the corner. At some point, I'm just going to clamp down on versions and just get something done, but I have the luxury of not having to now, so I won't. I think that's my biggest problem to productivity, luxury of time. Oh well... I have another project I am working on right now, but I guess I'll save that for another day. I'm waiting for my last folder to backup as I write this. Then I can remove all the hard drives attached to my system and finally get back to programming, although I might just call it a day soon. The whole backing up files and verifying stuff has been really draining to me, not to mention boring. But it must be done, so that's that.
  13. Thanks for the replies guys. I should just mention, I'm not the author of ENet or Netwerks! I kinda forget we have the source code to Netwerks as part of the SDK and the ENet module comes with Blitzmax (I'm a license holder). I guess I could make some changes to them both and send them to Josh later for some basic improvements in the library. I might end up doing that since it shouldn't be that hard. I'd still suggest people look into something higher level first if they are trying to make a game and need more features. However, for very simple programs and demos, it should be fine.
  14. The recent thread about Netwerks (wrapper for ENet) got me thinking some more about the library. I decided to fire up the latest source distribution and get hacking away at it. In this blog post, I'll share some of the modifications I have done to the library to make it a bit more usable as a core networking library to build on top of. Let me start off by saying that ENet is a pretty well designed low level library. I've always liked the design, which you can read about here. The documentation is rather sparse and the code has some areas where some more comments would help, but the source code is provided and it comes under a really friendly MIT-style license. There is some room for a few improvements with the library though. 1. Memory failure management - The library seems to have some mixed logic when it comes to memory allocation failures. There is code in certain portions that attempts to handle NULL pointers, but the main API function for allocation memory simple calls abort if a memory allocation fails. Naturally, in any program you don't want a library simply calling abort under the hood, leaving you and your users wondering what happened. The first set of changes I've made is to make the library consistently handle NULL memory allocations. This means at any time, if a memory allocation fails, the library will fall through execution and error out of the top level API calls without bringing down your entire application. Of course, I still have some more testing to do to make sure I didn't miss anything, but so far so good. For this set of modifications, I had to make a few API functions return error codes rather than nothing. This was done so at the first sign of any error, execution can be stopped by falling through rather than continuing operations and possibly corrupting vital data. I have added a Panic function that is called when a memory allocation failure is detected as well. I wanted to separate the responsibility of handling a critical error such as a memory allocation failure from the function that allocated memory. At least now if the programmer wants their application to abort on a memory error, they can do so with being able to know that is what happened first rather than being left in the dark. 2. Packet Size Limitation - In my reply earlier, I mentioned something about how ENet supports any sized packets through the use of fragments, but this can be exploited by clients to crash your server by requesting too much memory too fast, which inevitably leads to abort being called and taking down the server. Such bugs have been found in commercial mmo servers and as a result, malicious users have been able to negativity impact business. If you have the source code for ENet, all you have to do is find the line: totalLength = ENET_NET_TO_HOST_32 (command -> sendFragment.totalLength); Then, you can check against some predefined constant. If the packet is larger, I just return the result of enet_peer_disconnect (one of the functions I've modified to have a return value now, but under normal operations, it returns 0.) A change such as this is pretty vital to ensure clients cannot crash your server with huge packet allocation requests. 3. Callback function tracking - For this modification, I've defined a few sets of macros to compile in the __FUNCTION__, __LINE__, and __FILE__ preprocessor commands to report the malloc and free callback activity. This was done so I can track where allocation/deallocation requests are made throughout the library. I won't keep this modification in since it's served its purpose. It did help in showing where memory allocations were occurring in high frequencies. I'll take a look at changing the mechanics of memory allocation and deallocation in those areas to make the library more efficient and memory friendly (by using a boost pool for example). Other parts of the library only allocate memory once, so those areas are fine as-is. I like having debug messages outputted since trying to track through the Visual Studio debugger has a noticeable impact on performance when breakpoints trigger. Well those are the 3 main things I worked on tonight with ENet. The first two things are vital to making the library more usable in a commercial environment. The third was just to further my understanding of the library itself. I'm currently adding in some random memory failure tests to ensure at any time if a memory allocation fails, I've added logic in all the areas to handle it properly. I need to review all the code manually as well, but I'll have to do that on a fresh set of eyes. Tonight I'll run some simple client sending variable sized messages to the server to check for any bugs. I think I want to spend a little of time to get a better idea of how NetDog works through object replication by experimenting with an ENet base and build up my own system. I guess I could also check out RakNet's code, but I don't care for the code design. [Edit 1] After doing some testing, I had a few crashes whenever the server or client window was suspended and the resumed. I tracked down the bug to some logic I added to free packets on an error, but they were already being freed by the caller function. I checked all the code I added and double checked the logic of the calling function and removed my offending code where needed. Now, no more crashes! During that testing, I also decided to remove all the gotos in favor of returning a function call that wrapped the logic. While doing that, I realized I need to work in a better system for error propagation through the use of a global panic flag. Adding that logic was not hard at all, since I just wrote a small function with a static variable that's called before the panic function is invoked. Looking through the code some more, I need to add more parameter checking to handle NULL pointers. I'll get started on that now. [Edit 2] I've been cleaning up the library and condensing it into one file recently. I had to reformat the spacing by hand, which was not fun, but at least it's in a format I'm happy with. There's bound to be parts I've missed, so I'll have to make many passes through it in the upcoming days on fresh sets of eyes to find those inconsistencies. Likewise, I have to do the same to check error handling mechanics for all functions. I think I'll write a new public API for the library, and then wrap up calls to internal code with SEH exception handling to ensure if a fatal exception is generated inside the library, information about it will be able to be logged to help track down the problem.
  15. The article, 1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond, is one of the main references to use for RTS network programming to get an idea of the complexity involved. In addition, What every programmer needs to know about game networking, also gives some additional information regarding the topic. The networking model you would use for a RTS would be a "deterministic peer-to-peer lockstep networking model". The task of coming up with a deterministic simulation, regardless of game genre is no easy task. As soon as you use floating point types in your calculations or have say a physics system that uses floating points, determinism goes out the window. For such matters as those, you'd want to read up on Floating Point Determinism, which should give you a good idea of the things you need to keep in mind when implementing your logic. You can count on existing physics middleware not being deterministic by default, so you have to resort to using the fancy physics API to being graphic eye candy and implement your own system on top of it that you can guarantee to be deterministic if you need physics to be a core part of your RTS. Gamedev.net has some good threads to read up on comments about RTS network programming as well. Do a few searches there to get some additional ideas. From a thread there: I think the problem would be better solved with TCP since you need reliable, in order packets that are not going to be sent as a fast as the input would go in say a FPS game. The benefits of UDP are best seen when you can discard earlier packets in favor of more recent packets that are received, but such a model is not applicable in a RTS since everything has to be deterministic if you don't want the game to fall apart. You could make it work with UDP though, but it just depends on the final design of the game and how much data you are sending, which also affects how the TCP model would work as well. There's no easy answer here as compared with other game genres, but I'd just start with one that is easier for you to use and just try to make it to work. With that said, I'd suggest using RakNet over Netwerks if you are going with UDP for now. If you are trying to make a game and have not ever worked with TCP before, then I'd suggest sticking to an existing library rather than trying to write your own because of how much can go wrong. Existing libraries like RakNet are tried and tested and your current goal is to write a game, not a networking library. There are low level TCP supported libraries around, such as boost::asio or POCO, but I'd advise getting started with these for the same reason to avoid ENet/Netwerks: you want something higher level so you can focus on your game rather than the networking library. If you do have the time to spend to learn networking stuff though or it interests you, then by all means, try whatever you like. I ended up doing that and spent a while learning low level TCP and UDP concepts because I wanted to develop my own library rather than not understand what was going on under the hood of existing libraries. However, you will lose a lot of time and productivity, so I'd only recommend it if you aren't working on a main project. This is explained a bit in the earlier articles, but you'd have a central server for lobby, chat, match making and then have a client/server model where the 'server' is the peer host of the game. A P2P model can work, but if you notice in the 1500 archers article, it was programmed by Ensemble Studios, whom I'd safely say had more resources and people than any of us have access to. I think if you just stick to the Client/Server model of peers in the game, you'd be able to have less to deal with when it comes to managing connections. How exactly do networked games work? They work the same way a non-networked game works, with the exception a networked game has to process "messages" from external sources that occurred in the past (lag). A non-networked game can be thought of a specialization of a networked game where there is 0 network latency, since all events are processed immediately. You can easily simulate this as well. All you have to do is write a simple non-networked demo where you buffer key input by some delay (to simulate the ping to a server) and then after said time has elapsed, process those key inputs. You will notice the higher the delay is, the more inaccurate the simulation becomes. This is where Client Side Prediction comes into play as well as interpolation/extrapolation (see Entity Position Interpolation Code and Dead reckoning for example). How does one side keep track of the player on the other side or the ammo a player has or a the damage a players bullet does? Simply through the use of messages. In a client/authoritative server architecture, the server is the referee is validating events before passing them on to other clients. So a client connects to the server and the server creates a player object. This event is sent to other connected clients with the basic information of position, model, etc... If a client sends the event to shoot their BFG, it's the server's job to check to see if the player has that weapon first, and then if it has enough ammo to fire. If it does, then the server sends out the event. If it doesn't, the client gets a message that they are out of ammo and need to correct their game state with the server state. Object replication and state management are the key concepts here for such interactions. I know that packets are "data", but what defines a "message"? The programmer, you, defines network messages to mean certain things. Then, the client and server process those messages and update their simulation of the game world based on it, taking into account the time the event occurred. The easiest way to think about it is like the Win32 event model. When you get a message in your Window proc, you know how to process the message based on the specifications Microsoft has given you. So when you get a WM_KEYDOWN message, you know exactly how to use the wParam and lParam fields. The identifier WM_KEYDOWN is the opcode for the message and then the data is stored in two 32bit types. For your own networking messages, you might choose to have a byte (256 unique), word (65k unique), or dword (4bln unique) opcode and then based on that opcode, you define the structure for the message. This is what defines your networking protocol. This protocol is the same type of protocol that you are already using when you use TCP or UDP. In either of those links, you'll see the format of the underlying packets that are sent across that protocol. So, let's say you are using UDP and your packet protocol is as follows: Header Opcode [2 bytes] Body Data [0..1022 bytes] Let's say you assign opcode 0 to be an operation where the client sends their name. Let's say this is your first time designing packets, so you implement it like you would in a regular program. The higher level packet structure for opcode 0 would look like: word opcode int nameLength char * name To process the packet, your logic would look something like: void ParseOpcode0() { nameLength = ReadInt(); std::string tmpName; tmpName.resize(nameLength); ReadArray(&tmpName[0], nameLength); } Looks harmless enough, right? The golden rule of network programming is to never trust client data. Many professional commercial companies forget this rule all the time and pay as a result when hackers exploit their systems using flaws in their code. The "correct" way to parse that packet would be as follows: void ParseOpcode0() { nameLength = ReadInt(); if(nameLength < 0 || nameLength > 16) // let's say 16 max characters for name // log error and disconnect client std::string tmpName; tmpName.resize(nameLength); ReadArray(&tmpName[0], nameLength); for(int x = 0; x < nameLength; ++x) // validate character tmpName[x] to ensure it's alpha numeric, _, or any other rules that apply // Now validate contents of string, i.e. filter curse words, GM, admin, etc..., make sure first 3 characters are letters, w/e } Pretty much any message you define, you need to make sure to validate the contents during parsing and be sure to handle all possible problems. This is always easier said than done, but it's only a small part of the challenge of network programming. You also need to take into account Endianness if you are going to be working across different platforms and are going to be sending data back and forth. Otherwise, a value like 0x0000000F on a little-endian machine turns out to be 0x0F000000 on a big-endian machine, which is way off and can wreck havoc on your system (most people run into this problem when interfacing with java servers). Another issue to consider is signed/unsigned data types and how overflow can affect your logic if you do not properly check values. For example, in the code above, since nameLength was an integer, a negative value is possible, so you need to check < 0 and > max. Let's say you had an unsigned value and only checked > max, which would work fine in this case, but if the type ever were to change back to a signed int, then you have a bug in the logic and managing issues like those are a real pain once you enter production. As you program, you have to keep such things in mind and really pay attention to the logic operations you perform on different data types. That was just an example, ideally, your nameLengh would just be an unsigned character to save space and further help prevent overflow issues since 255 is the max size possible. Taking things one step further, packet formats can vary based on flags set in the data. Let's say you have a multi-format packet like this: word opcode int id bool hasName if hasName int nameLength char * name endif bool hasPosition if hasPosition float x float y float z endif Your processing logic would simply follow the structure and parse out the packet as it was sent. So, without error checking and validation: id = ReadInt() hasName = ReadByte() if(hasName != 0) { nameLength = ReadInt(); ReadArray(&tmpName[0], nameLength); } hasPosition = ReadByte() if(hasPosition != 0) { x = ReadFloat(); y = ReadFloat(); z = ReadFloat(); } So, packets can get quite complex. With input driven games like a RTS or FPS, you won't have such complicated packets, but you'd see similar in games that deal with persistent worlds. Object Replication - The ability to create, destroy, serialize, and transmit game objects. RakNet offers the ReplicaManager3 interface for this (I've never used it myself though). NetDog's interface for this can be found here: Objects. State Management - Maintaining a synchronized state of all objects in your game as needed across the network. For example, this is the interface NetDog provides: Object Driven Model Tutorial. Patching - Traditional sense. If you want to have the means to update client files through the game itself (think content downloads, maps, media, etc..) rather than having to do it externally, this is important. Some of these things might seem easy at first glance, but they definitely aren't! Depending on your game type, you will have different needs and requirements to different degrees. In a RTS, you are working with commands being sent over the network, so you don't have much object replication going on. Instead, you have to worry about state management to ensure all simulations are synchronized. Hope that helps some. There is a lot of theory to game network programming and it can get quite complex. There's also a lot of different perspectives and opinions about how things can (or should) be done, so just take this post as one perspective on the matter. There may be some inaccuracies here and there in my post, so make sure to do some more research on anything I've mentioned for other explanations. Good luck!
  16. The Netwerks library is a wrapper for ENet, which is a low level UDP networking library. Basically, it's good for simple tech demos or really simple games that just need to pass data around with minimal networking requirements and no real need for higher level interactivity. If you needed higher level features, such as lobby systems, auto patching, object replication and state management, you'd have to code all those yourself. If you are going to use this library through Netwerks or standalone, I'd suggest taking a look through the source to understand the internals better. I've made a little demo showing the main uses of the Netwerks API. However, I can't recommend using it for your project because of the alternatives that are available. RakNet is pretty much the go to guy for higher level UDP networking and has a pretty indy friendly license: basically, once you make over 250k gross, then you need to buy a license for 5k per sku. Until then, you can use it for free so it's a win-win. Back to Netwerks, I don't have the setup to make a video or PDF, so here's code that's commented that shows the main uses. Server: http://drewbenton.pastebin.com/x8MjCU1D Client: http://drewbenton.pastebin.com/F5DY37F9 Sometimes the demos crash on exit, so that leads me to believe there are some memory management issues between the DLL and the EXE. I've tried all combinations of not calling the FreePacket function and it still does it. I'm not sure how different the wrapper for ENet is than to the ENet API itself so Josh would need to have a look at that. Honestly though, I'd suggest Josh just rip out Netwerks from the engine. In the past, there was a lot of pressure by people to have networking built in, but it's just not practical in its current form. Network programming is really tricky in that you have to make sure errors in the system don't bring down your entire application or corrupt data. There is a lot of theory in this, so my suggestion is to use a higher level networking library to take care of these things so you don't have to yet. Right now, I am learning NetDog because it is designed to handle certain things that you'd have to otherwise code yourself, all of which are not trivial. I can't comment on if it's worth licensing yet, but after I get some more work done with it I'll make a post about it. Anyways, when using Netwerks, the basic of all your operations revolves around defining packets and then processing those packets. In my example, I used a 1 byte field for the opcode, but realistically, you might end up using at least 2 bytes to support over 256 messages if your game is that complex. I also added a very simple object replication system that stores events and then sends them back to the newly connecting clients. You have to account for a lot of other things for such a system, so it's not as trivial as it's shown there. You have to ensure objects still exist, send state information if they are moving, and so on. I'd definitely suggest checking out RakNet for your project though. In the past, when RakNet was not free to use at all, suggesting it would have been out of the question, but now that it is, you really can't beat it. I'd only suggest using ENet from source and only if you already have experience in the networking domain since you will more than likely make some changes to the core library as you go along. When I last used it for a tech demo in trying out IOCP w/ UDP, I noticed how it'd be possible for a malicious client to crash the server by sending packets of large lengths over and over since ENet supports any sized packets through fragmenting. I've done a bit of networking stuff the past couple of years, so if you have any questions feel free to ask!
  17. This post will talk about the (E)mbedded (W)eb (S)erver (I)nterface library I have been working on the past couple of days. In addition, this will probably be the last major post about the library for a while since I need to move on to more tech demos of other things. I'm not going to give up the project, but I've reached a point where I need to work on other things before investing more time on using the library in a practical application. In other words, I've put in the time to developing the library now because later I'll have a need for it and it'll be ready. The purpose of the EWSI library is to provide a simple and easy to use interface to develop an embedded web server in your application. The library gives you all of the basic tools you need to be able to utilize existing web technology in your own applications. The library itself does not actually implement a full web server; that is the task of the end user. This library saves an end user the time from: Having to create a TCP server, processing basic messages sent over HTTP, creating a C interface that allows the library to be utilized by many languages on the Windows platform, creating a Lua binding for the API, and creating a library that is meant to be reused in a multitude of applications. So, to be clear, this library does not provide anything that an ordinary programmer could not come up on their own. There is nothing new or innovative about this library. Instead, it provides a convenience by saving time for the end user so they do not have to invest the time into coming up with their own solution for the task. This of course, is why most middleware is created. The EWSI library was designed operate from the end user's main thread. This was done to ensure it'd be compatible with virtually any project. It is a thread safe library, so it can be used from multiple threads if needed. If Lua is being utilized, then the library should not be used in a multithreaded environment due to the nature of Lua not being thread safe by default. The Lua bindings that is provided through the engine API has a limitation of being able to support only one Lua state at this time. The basic major execution flow for the EWSI library is as follows: 1. The user loads the EWSI library and registers an error callback function. 2. The user creates a server and registers callback functions. 3. The user calls an EWSI API function to update the server once per their main loop cycle if running in a single thread 4. The user handles any events as the callback functions are invoked. 5. The user unloads the EWSI library when their application is about to close. There are four types of callbacks right now. There is a global error callback that is invoked whenever there is an error generated in the library. There are also callbacks for the server when a client connections, a client generates an error, and a client makes a request. Through logic implemented into these callbacks, a web server is created. A basic static content serving server would just process the path of the GET requests clients generated. It'd load files from disk and send them back to the client or send 404 Not Founds if the content didn't exit. A dynamic content serving server would work similar to PHP or ASP for example. The server would load templates of pages and then during request processing, fill in content as needed and return the resulting output to the client. This is a more advanced concept, but all it requires is an end user to implement their own content processing logic and then send the resulting output just as it'd be done with static content. The EWSI library itself gives you the core tools to do whatever you need to (although there are certain limitations right now in terms of the internal HTTP processing since it's simple). Now for the API of the EWSI: http://drewbenton.pastebin.com/WK4QumKj The API has been designed with simplicity in mind as well as ease of interoperability with other languages. As a result, integers are used for pseudo-handles rather than pointers. Rather than worrying about allocation issues between a DLL and an EXE host, the library provides the means for a user to obtain how much memory will be needed first. Then, the user can allocate the memory however they need to on their side and pass it to through the library API functions for data to be written into the memory. Naturally, to use this library with other languages, the language has to support being able to load DLLs, allocate contiguous blocks of memory, and support stdcall calling conventions. Here is a full example of the EWSI C API in action: http://drewbenton.pastebin.com/Q0efMxJX This program shows off the usage of all API functions, excluding the Lua one which comes next. Rather than handle each error, abort() is put into place of where error handling logic should go. The demo shows "Hello World" in the web browser on any request and outputs debug information as it runs. The Lua bindings provided internally try to mirror API access as consistently as possible. However, there are some mechanics that would not make sense, so they are changed slightly. An example of a Lua program that uses the Lua API for the EWSI can be found here: http://drewbenton.pastebin.com/WbZZXCb0 As you might notice, any API function that will copy data does not require the length of the input buffer as that is handled internally. Rather than pass a buffer, a global variable name is used. The host program that calls that Lua script can be seen here: http://drewbenton.pastebin.com/YCMXYgyQ Attached is a (binary version) package that can be used for playing around with it if anyone is interested. Please read the license. I am not currently setup with my own site, forums, instant messaging, or anything else for dealing with any of my work I release. Until I am, I don't want anyone coding their projects into a corner with a dependency on something I've written that I can't support, take suggestions for, track bugs, or update on a regular schedule. Eventually, I'll be properly setup to handle all my projects and releases in an organized manner. Having seen how nice IP.Board is here at Leadwerks, I'd definitely setup a system using that technology instead of VBulletin. I've never been overly happy about the feel of VB whereas I loved this IPB the first time I browsed the site and started posting. Anyways, that pretty much wraps up this post about the EWSI library. I'm off to start working on some new tech demos utilizing Leadwerks 2.3 now. Thanks for reading!
  18. Thanks for the reply! Hopefully it'll be able to be used for some cool things. I''ll have a new post on the library itself soon. I think I've gotten it to a point where I'm comfortable with the features it provides and hopefully it doesn't have too many bugs. There are existing web servers that might be able to be used or modified for such a task as this, but I wanted to have my "own" basic one that provides the simple features needed. I know exactly how this works and I think the design is practical. Anyways, back to work on checking over a few things on the project and then begin writing a new blog post.
  19. In my last blog post, "Why didn't I think about this earlier..." moment, I talked a little about how cool I thought the idea of having an embedded web server would be in your applications. So, for the past couple of days, I've spent some time implementing the idea and coming up with a simple tech demo alongside Leadwerks to show off the basics. First, I had to code a simple TCP server. This was an easy task for me since I've kind of specialized in network programming for the past couple of years. For this particular task, I decided to just go with a simple select server, limited to 64 max concurrent connections. I went this route because I want something very simple and usable. I didn't want to drag in a boost dependency to use boost::asio since this embedded web server isn't going to function as a hosted web server would. Once I had my simple TCP server coded, I needed to then implement a more specialized HTTP processing server on top of it. The HTTP protocol is not fully implemented in this case because I am not trying to write a full blown web server as much as just a basic one. I have support for simple GETs and POSTs. Since the web browser client handles most of everything else, the simple server just has to send correctly formatted messages to it. At this point, I now have the basics of everything I need for the project. However, I wanted to create a library that would be usable from many languages in the same style that Leadwerks is. So, I spent some time developing a C API along with DLLs that are runtime loaded. This way, end users can easily drop in the library into their projects and remove it if they do not want it with minimal work. I went ahead and made this a full blow library in terms of making sure to document the public API as well as providing the loading code for C/C++ users. It's not in a "final" stage yet, but as I work with it, I find little things here and there to improve. Now that the library is completed though, I can just drop it into a Leadwerks project and begin coding. For the tech demo, I made some simple GET processing logic to handle FPS, UPS, the current frame being displayed, as well as a simple Admin panel that links to the previous three features. It's nothing complex really and it's all hard coded. In future versions, a dynamic content system would really allow for more flexibility. Such a system is possible now, but I'm just taking baby steps in testing out the proposed technology. Here are some screenshots of the project. 1.) When the program is not running and you try to access the server, you will get an error since the server only runs when the application is. 2.) This is the console output after trying to access a page that didn't exist. In this case, the root path which is not currently configured. 3.) Boring 404 not found page implemented for everything that is not currently setup. 4.) The simple admin page. 1.) The output of the frame page, which is a screenshot of the program taken at the time the page was accessed. 2.) Simple UPS page that tells the current UPS on access. 3.) Simple FPS page that tells the current FPS. Here is what the code for that application looks like: http://drewbenton.pastebin.com/XsWRLSk4 The UserOnProcess function shows the current API usage of the library that was developed. As mentioned before, everything is hard coded. It'd not take much work to switch over to a dynamic content loading system, but that will come later. Attached is a demo of the whole thing if anyone wants to try it. All you have to do is run the program and then type in: "http://localhost:7777/admin" in your web browser. From there, you can try out the current functionality. Since the "frame" command saves a file to the current directory, make sure to execute the application in a folder where default write permissions are applied if you are on Vista or Windows 7. You can hit Refresh on pages to get updated content as well. I plan on making a new blog post about the embedded web server library itself later after a few more revisions. Overall I am very pleased with how this project has turned out. I see some real potential here and hopefully I can work up some more complicated examples later on to show off possible uses. Check back later for more!
  20. Recently I was doing research on some stuff when I came across this article: Web-Based Real-Time Application Analysis. I gave it a read through, modified the code a little, and tested it out. Sure enough, it worked. Suddenly, the light bulbs in my head started lighting up. Why didn't I think about this earlier?! See, in the past, I've only through to use my application to generate web friendly log reports (an example) or used my application front end to communicate with a server back end. Never have I thought to make the application itself the back end and the web browser the front end. I mean it sounds simple enough, but this represents an entirely new thought process that I've never had before on my own. I've always looked at using the technology the other way around. With this new perspective, I think I can finally come up with some useful fun stuff for application debugging and reporting that I've never been able to accomplish before. I've been teeming with excitement all day (well, only been awake <4 hrs now) as I work on getting a prototype done. Some people might wonder why this is a big deal, so let me give some examples of what I am visualizing here. 1. Extensible real time reporting. Since the web browser is the front end and everything is being done through the HTTP protocol, the embedded server can generate html/javascript and stream it to the web client in a manner that is easy to interpret. I.e., no more log file parsing and custom applications to sort and display what you want, you can do it all through html/javascript (*some restrictions apply). 2. Real time debugging. Since everything is operating over the TCP/HTTP protocols, all a user has to do is worry about logic. There's no need for custom GUIs to be coded as much as just scripting simple but flexible HTML GUIs rendered by the web browser. If your project utilizes Lua, then you can easily pass Lua commands to the application from a web browser using a form, for example. With such a setup, you can actually modify stuff as the programs running from the web browser without having to really do anything since everything is in place. 3. Costless, lightweight, simple, proven technology (http protocol, tcp protocol, html/css/js/ajax/etc...) that anyone can utilize really. Everyone has a web browser and as long as you don't get too fancy with css/js/ajax, you won't have that much work to do to get it rendering correctly across different browsers. Adding the custom embedded server to your project will be pain free and can easily be removed if needed (still working on the design though). For a multiplayer game, having a built in web based administrator console seems very useful. You could also easily integrate it with a database to further provide more flexibility. I think I'd throw in sqlite to the application as well. The idea is to get out of the 'norm' of things programming wise and start to look at alternatives that might not be in use. I know the phrase, "if it ain't broke, don't fix it", but if we just stuck to that, we'd never see any improvements in the field. I mean it's 2010, it seems we are still fighting with design issues that have been around for decades. It's time for something new. Anyways, I think there's potential here. Maybe there's none. I guess I won't know until I find out. I'm going to make a good effort to try and come up with a Leadwerks specific application of this idea. I should note, since it might be mentioned, this idea has nothing to do with the current Leadwerks Editor (which I'm still anxious to use). This idea is more general and for things that don't already have a means to easily interface with a program externally. Perhaps it's something that could be turned into a product even. Who knows, I'm getting ahead of myself. Well, that's what I'll be working on now. Sorry CEGUI Layout Editor, but you're on hold for the moment. Thanks for reading!
  21. I don't have a comparison to other engines, but one thing I do like about the Leadwerks entity system design is that it's very easy to use, it's intuitive, and it's complete*.(In my limited experiences, I've not found something that wasn't possible with our current command set yet.) Right now, I am working on some network stuff and I want to design a Lag Compensation system as described in the Source Multiplayer Networking article. With our current entity command set, the task is so trivial it's almost laughable how easy it is to implement with Leadwerks. For that task, all it takes is use of SetEntityMatrix, GetEntityMatrix, SetEntityCallback, GetEntityUserData, and SetEntityUserData. From there, it's just a matter of putting together the logic for storing object history in a circular buffer design. With that system in place, I can now just use the other engine commands for picking and ray casting without having to do any real 'work' myself when it comes to math or restoring objects to a previous position. I will have to interpolate between two of those matrix states, but that's trivial since Linear Interpolation will be "good enough" for my tech demo. Basically, I can just focus on implementing logic rather than having to figure out everything that comes up to that point. It's great! This is a rough draft example of what I am talking about. This is basically the code I ended up with to see if implementing lag compensation would be feasible or not with Leadwerks. #include <engine.h> float global_start_time = 0; const unsigned char MAX_HISTORY_STATES = 125; bool updateHistory = true; bool update = true; TEntity camera; struct THistory { unsigned char viewHistoryIndex; unsigned char realHistoryIndex; unsigned char maxHistoryIndex; float times[MAX_HISTORY_STATES]; TVec16 history[MAX_HISTORY_STATES]; }; void __stdcall FreeEntityCallback(TEntity entity) { THistory * pEntityHistory = reinterpret_cast<THistory *>(GetEntityUserData(entity)); SetEntityUserData(entity, 0); delete pEntityHistory; } void __stdcall UpdateEntityCallback(TEntity entity) { THistory * pEntityHistory = reinterpret_cast<THistory *>(GetEntityUserData(entity)); if(pEntityHistory && updateHistory) { pEntityHistory->history[pEntityHistory->viewHistoryIndex] = GetEntityMatrix(entity); pEntityHistory->times[pEntityHistory->viewHistoryIndex] = AppTime() - global_start_time; printf("pEntityHistory->viewHistoryIndex: %i\n", pEntityHistory->viewHistoryIndex); pEntityHistory->viewHistoryIndex++; if(pEntityHistory->maxHistoryIndex != MAX_HISTORY_STATES) pEntityHistory->maxHistoryIndex++; if(pEntityHistory->viewHistoryIndex == MAX_HISTORY_STATES) { updateHistory = false; update = false; } } } int __stdcall CleanupEntity(TEntity entity, byte* extra) { printf("Free: %s\n", GetEntityKey(entity, "name")); FreeEntity(entity); return 1; } void TrackEntity(TEntity entity) { THistory * pEntityHistory = reinterpret_cast<THistory *>(malloc(sizeof(THistory))); memset(pEntityHistory, 0, sizeof(THistory)); SetEntityUserData(entity, (byte *)pEntityHistory); SetEntityCallback(entity, (byte *)FreeEntityCallback, ENTITYCALLBACK_FREE); SetEntityCallback(entity, (byte *)UpdateEntityCallback, ENTITYCALLBACK_UPDATE); } void pickedAddForce(const TPick& pick) { TBody body = GetParent(pick.entity); TVec3 campos = EntityPosition(camera); TVec3 force = Vec3(pick.X - campos.X, pick.Y - campos.Y, pick.Z - campos.Z); Normalize(force); force.X *= 30.0f; force.Y *= 30.0f; force.Z *= 30.0f; AddBodyForceAtPoint(body,force,Vec3(pick.X, pick.Y, pick.Z)); } int main(int argc, char * argv[]) { Initialize(); Graphics(1024, 768); TWorld world = CreateWorld(); camera = CreateCamera(); SetEntityKey(camera, "name", "camera"); PositionEntity(camera, Vec3(0, 0, -5)); TEntity light = CreateDirectionalLight(); SetEntityKey(light, "name", "light"); RotateEntity(light, Vec3(45, 45, 0)); TBuffer buffer = CreateBuffer(GraphicsWidth(), GraphicsHeight(), BUFFER_COLOR | BUFFER_DEPTH | BUFFER_NORMAL); TBody cube_body = CreateBodyBox(); SetEntityKey(cube_body, "name", "cube_body"); SetBodyMass(cube_body, 1); TMesh cube_mesh = CreateCube(); SetEntityKey(cube_mesh, "name", "cube_mesh"); EntityParent(cube_mesh, cube_body); PositionEntity(cube_body, Vec3(0, 3, 0)); RotateEntity(cube_body, Vec3(45, 45, 45)); TrackEntity(cube_body); TBody ground_body = CreateBodyBox(10, 0.1f, 10); SetEntityKey(ground_body, "name", "ground_body"); TMesh ground_mesh = CreateCube(); SetEntityKey(ground_mesh, "name", "ground_mesh"); ScaleEntity(ground_mesh, Vec3(10, 0.1f, 10)); EntityParent(ground_mesh, ground_body); PositionEntity(ground_body, Vec3(0, -1, 0)); Collisions(1, 1, 1); EntityType(cube_body, 1); EntityType(ground_body, 1); UpdateAppTime(); global_start_time = AppTime(); TEntity e = cube_body; while(!KeyDown(KEY_ESCAPE)) { SetBuffer(buffer); RenderWorld(); SetBuffer(BackBuffer()); RenderLights(buffer); DrawText(0, 0, "update: %i", update); Flip(); if(MouseHit(1)) { TPick picked; if(CameraPick(&picked, camera, Vec3((flt)MouseX(), (flt)MouseY(), 1000), 0, 0, 0)) { if(!update) { MessageBoxA(0, GetEntityKey(picked.entity, "name", ""), "", 0); } else { pickedAddForce(picked); } } } if(!update) { if(KeyDown(KEY_LEFT) || KeyHit(KEY_UP)) { THistory * pEntityHistory = reinterpret_cast<THistory *>(GetEntityUserData(e)); if(pEntityHistory) { if(pEntityHistory->viewHistoryIndex > 0) pEntityHistory->viewHistoryIndex--; SetEntityMatrix(e, pEntityHistory->history[pEntityHistory->viewHistoryIndex]); printf("[%i] History from time: %.0f\n", pEntityHistory->viewHistoryIndex, pEntityHistory->times[pEntityHistory->viewHistoryIndex]); } } else if(KeyDown(KEY_RIGHT) || KeyHit(KEY_DOWN)) { THistory * pEntityHistory = reinterpret_cast<THistory *>(GetEntityUserData(e)); if(pEntityHistory) { if(pEntityHistory->viewHistoryIndex < MAX_HISTORY_STATES - 1) pEntityHistory->viewHistoryIndex++; SetEntityMatrix(e, pEntityHistory->history[pEntityHistory->viewHistoryIndex]); printf("[%i] History from time: %.0f\n", pEntityHistory->viewHistoryIndex, pEntityHistory->times[pEntityHistory->viewHistoryIndex]); } } } if(update) { UpdateAppTime(); UpdateWorld(); } } ForEachEntityDo((byte*)CleanupEntity); return 0; } Of course, that code is going to change greatly since it was a first attempt, but it's just an example of what I am talking about.
  22. Progress has been good over the past few days of working on the editor. I've been implementing more logic for handling the designing of GUIs as well as adding some features that are vital for efficient GUI designing. I still have a bit of work to do and code to cleanup, but things are looking very good right now. Here are some updated development screenshots. I added the ability to easily resize the application to commonly used screen resolutions. I took this idea from the original CELayoutEditor because it allows designers to get an idea of proportions across different sized screens. I'll add the ability to hide the property inspection as well later. I added a "locking" feature to controls to prevent them from being modified. I felt that was a necessary feature to help keep from messing up your GUI accidentally as you work with more controls. In this image, I show the logic of not being able to delete a group of controls if one is locked. This is the only operation implemented this way. If you were to instead move or resize those controls, the action would only process for unlocked controls. I load the available types of controls from a CEGUI scheme file, but have added in hard coded logic to only show the controls implemented. Each control type has additional fields for default X, Y, Width, and Height that you can set by expanding the menu on the right. This was done so you don't always have to have controls placed in inconvenient locations or sizes each time. My current "todo" list for this project: - Code project save/load system - Code the export to CEGUI system - Implement more base controls - Implement custom controls that are themed to CEGUI graphics - Implement custom objects that hold special properties for different types of CEGUI components (right now all controls share the same base class) - Tie in the Undo/Redo and Cut/Copy/Paste systems (part of the base code I am using) - Implement control formatting (align, size, space) - Implement "Select All" - Implement keyboard accelerators (ctrl + keys) - Implement user settings save/load for application - Code cleanup and optimization I've had to come up with some "workarounds" due to the design of some C# controls and how the internals work. The "Locking" system was tricky because components are subclassed for resizing and if you try to cancel events for the resize via grips, your application crashes with an exception. Likewise, resizing controls in a certain fashion will result them moving, so I had to come up with a way to avoid all of that. I just saved the last value on each change and if it's locked, restore those values. The mouse position required a bit of extra work because of how events are sent to the controls themselves. As a result, I could be at 400, 300 in the GUI but it'd be 0, 0 if it were on the top left corner of a control. For this issue I just get the location of the control and add offsets of the mouse move message to get the cursor position when it is inside the DesignSurface object. Maintaining the correct View required a bit of thought as well for a polished system. When the user maximizes the application and then restores it, I had to come up with a simple way to restore the selection. This turned out to be a simple storing the last control selected and detecting the maximize event from a Resize and setting it accordingly. That's about it for now. I'll probably slow down development this weekend on it to work on a few other things and then get back to full steam once the week starts.
  23. Thanks! I decided to finally commit to using CEGUI for a project because I've always used the whole roll your own approach. However, after all these years, I still don't have something to reuse or anything of any worth to myself or others. CEGUI has a lot of gripes and I'm not ecstatic about it as a library, but I've come to the realization that my development practice of always wanting to roll my own components has led me to where I am today in regards to game development: nowhere. I do enjoy the educational experiences of learning how things work myself though. I figure if I take the time to just learn CEGUI, it's going to take me one step closer to my goals. I'll end up reusing it over and over in all my future projects as well, so I think it will pay off as an investment. I'll be able to join other projects as well and bring that experience to the table with something to show for, for once. Some people can make it work the other way, but I've learned about myself that I can't. I'd like to think I can and I want to, but I'm fooling myself. I simply need to pick something worthwhile to invest in and stick with it until I finish a project. Otherwise, I'm going to never get anything done! I guess that's why I fell in love with the Leadwerks engine. It's easy to use, the API design is my type of design, and it can produce results if you are willing to work for it. There is no reason why I should fail my next project with these tools at hand. Anyways, hopefully the tool will be good enough to get a few people to at least want to try it out. I'm adding more stuff as we speak and it's going well. Even though it's what I expected from using .Net, I'm still amazed at the productivity that is possible with it. I mean it's like I just have to think of what I want and implementing it is only a matter of minutes and some Google searching to get it implemented. I really enjoy it. *High Five*! I actually started out learning a lot of RAD in C++ via Borland C++ Builder (3.0) as well. I guess it's not that long ago, but it would have been about 8-10 years ago. It certainly seems like a lot longer than that though. It was a lot of fun. I never got into much VB programming though, so I never had that to mess with. I remember trying to use Visual C++ 1.0 and it was just all over my head at the time. I had wanted to work on a C++ IDE that would make programming a lot faster (in theory) through the use of snippets. The idea was to click buttons and fill out different parts of control structures, like for, while, etc... I didn't ever get too far with it and it wasn't a well through out idea, but it seemed like fun at the time. I did a bit of other stuff with BCB in my spare time, but I think I lost all that work over the years. In high school, we were using Turbo C++ (DOS version) during that time. Anyways, I'll stop with the nostalgia for now.
  24. For my current Leadwerks project, I need to setup some GUI panels for logging into the game server. The way CEGUI is setup makes writing your own layout very hard by hand. Just take a look at this layout for example. Yea, good luck there. I gave the Official CEGUI Layout Editor a try. I was hoping I could easily mock up some basic screens that looked nice and just be done with it. Boy was I wrong. The biggest problem is having to work in the unified coordinate system. Now, while the UCS is a great idea for the internals of a GUI system, it's not a "human" friendly system to actually design with. The visual editing is good enough for rough mock-ups, but when you have to size components the same, align them, and so on, it's nay impossible! The next problem has to do with the actual implementation of the editor. When you go to select controls, they tend to move. This means you can have everything positioned just how you want it and then mess it up by accident and have to redo it since there is no undo system. Needless to say, trying to design some GUI screens via the editor was very frustrating. So frustrating, in fact, that I gave up trying to use the tool and decided to write my own. LayoutEditor.Net is my attempt to write a better CEGUI Layout Editor. I am using C# for this tool because GUI development in C# is fast, easy, and simple. From previous experiences, I've been able to design complex GUI based applications with full polish in a matter of days whereas trying to do the same in C++ would take weeks to months. So far, it's been only a couple of days and progress has been going great. I am using the .Net DesignSurface component as the primary driver. If you have ever used Visual Studio's GUI editor, then this component is what you are using through the IDE. Tack on a PropertyGrid control and you have the basics of an editor in a matter of minutes. I used this article, Have a Great DesignTime Experience with a Powerful DesignSurface (Extended) Class, as the start for the project. Since I'm not going to be designing a Window's GUI, but rather a custom CEGUI, I've had to work out some design differences in how everything works together. This involves creating some custom classes to handle the CEGUI properties and map them to Window's Control properties where applicable. I still have a bit of work to do and then comes the polish and testing phases, but I am looking forward to completing this project. Here are a couple of development screens of what I have so far: The amazing thing is that there is not that much code, which is something I've always loved about .Net. It really gives new meaning to RAD. I couldn't imagine doing this task in any other language or framework. I was about to start the project in MaxGui, since I'm a license holder, but then I remembered how efficient .Net was. Also, I remembered why I hated MaxIDE so much after working in it for a couple of minutes. My Blide update license ran out earlier this year, so I didn't want to invest any more money into Blitzmax. I really didn't want to get side tracked from my project, but if there's one thing I've learned about development is that tools matter. The week or so I spend working on this project will pay off in the future as I need to design more GUI screens for my project. Rather than spend time fighting the CEGUI Layout Editor, I'm just going to make a tool that I know will let me get what I need to done fast and easy. Perhaps some other people might be interested in the project for their own work as well. I've always loved tool development, so hopefully I can come up with something useful. On that note, I'm going to be upgrading to Leadwerks 2.3 soon for the new tools, which should make development even more fun and exciting. Hopefully, I'll be able to get a real project completed in 2010 rather than dillydallying around with no real focus. I've been trying to make a real game since 2004 and I've yet to get anything past simple 2D stuff. I think I've reached a point where I can do it now. Anyways, hopefully I'll have this all done in no more than a weeks time. If anyone is interested or has some ides about the project, leave a message!
  25. Yea, I actually have two versions of CEGUI and Leadwerks (2.8) currently. I didn't ever update that old thread back in the day due to project interest reasons, but here's some information and opinions on the matter if you are still looking into this topic. Version 1: Leadwerks + CEGUI Integrating CEGUI into Leadwerks alone is very simple. All you literally have to do is create the CEGUI OpenGLRenderer after Leadwerks has been started and you are set. While this is quick and easy, you start to run into performance issues when injecting input into CEGUI from Leadwerks. Since all input calls have to be polled through the API, you will be continuously having to inject input state which can be very inefficient when no events are actually happening. On my PC at least, it kind of feels 'sluggish' from always injecting the mouse position each frame. This could be optimized of course, but I'd rather do something that is shown in the second version below. Here is a simple example that utilizes the CEGUI Lua scripting module and works alongside Leadwerks. I just load the GUI itself from one of the CEGUI scripts that comes with the SDK. (Note: code is not 'complete' yet, still looking at some issues with OGL state changes, so that's why those 3 commands from before are missing) #include "engine.h" #include <CEGUI.h> #include <RendererModules/OpenGL/CEGUIOpenGLRenderer.h> #include <scriptingModules/LuaScriptModule/CEGUILua.h> #include <lua.hpp> int main(int argc, char** argv) { lua_State * lua = lua_open(); luaL_openlibs(lua); Initialize(); Graphics(800, 600); CEGUI::OpenGLRenderer & renderer = CEGUI::OpenGLRenderer::create(); CEGUI::System & sys = CEGUI::System::create(renderer); CEGUI::LuaScriptModule & script_module = CEGUI::LuaScriptModule::create(lua); sys.setScriptingModule(&script_module); CEGUI::DefaultResourceProvider* rp = static_cast<CEGUI::DefaultResourceProvider*>(CEGUI::System::getSingleton().getResourceProvider()); const char* dataPathPrefix = "data"; char resourcePath[MAX_PATH]; _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "schemes/"); rp->setResourceGroupDirectory("schemes", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "imagesets/"); rp->setResourceGroupDirectory("imagesets", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "fonts/"); rp->setResourceGroupDirectory("fonts", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "layouts/"); rp->setResourceGroupDirectory("layouts", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "looknfeel/"); rp->setResourceGroupDirectory("looknfeels", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "lua_scripts/"); rp->setResourceGroupDirectory("lua_scripts", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "xml_schemas/"); rp->setResourceGroupDirectory("schemas", resourcePath); CEGUI::Imageset::setDefaultResourceGroup("imagesets"); CEGUI::Font::setDefaultResourceGroup("fonts"); CEGUI::Scheme::setDefaultResourceGroup("schemes"); CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels"); CEGUI::WindowManager::setDefaultResourceGroup("layouts"); CEGUI::ScriptModule::setDefaultResourceGroup("lua_scripts"); CEGUI::XMLParser* parser = CEGUI::System::getSingleton().getXMLParser(); if(parser->isPropertyPresent("SchemaDefaultResourceGroup")) parser->setProperty("SchemaDefaultResourceGroup", "schemas"); script_module.executeScriptFile("demo8.lua", ""); if(!CreateWorld()) { MessageBoxA(0, "Failed to create world.", "Error", MB_ICONERROR); return Terminate(); } TEntity c = CreateCube(); TCamera cam=CreateCamera(); CameraClearColor(cam,Vec4(0,0,1,1)); MoveEntity(cam,Vec3(0,0,-10)); TLight light=CreateDirectionalLight(); RotateEntity(light,Vec3(65,45,0)); TBuffer buffer=CreateBuffer(GraphicsWidth(), GraphicsHeight(), BUFFER_COLOR | BUFFER_DEPTH | BUFFER_NORMAL); HideMouse(); while(!KeyHit(KEY_ESCAPE)) { TurnEntity(c, Vec3(0, 1, 0)); UpdateWorld(); SetBuffer(buffer); RenderWorld(); SetBuffer(BackBuffer()); RenderLights(buffer); CEGUI::System::getSingleton().renderGUI(); Flip(); CEGUI::System::getSingleton().injectMousePosition(MouseX(), MouseY()); } sys.setScriptingModule(0); CEGUI::LuaScriptModule::destroy(script_module); CEGUI::System::destroy(); CEGUI::OpenGLRenderer::destroy(renderer); lua_close(lua); lua = 0; return Terminate(); } So this is the quick and easy way. It 'works', but you will start to run into some event issues due to what is exposed to us in the C API of Leadwerks. This leads to the second version. Version 2: Leadwerks + CEGUI + SFML The direction I am going to go in is using another framework to manage the window that Leadwerks creates. By doing this, I know have access to all the events that are otherwise taken care of internally by the engine. As a result, CEGUI works more smoothly with Leadwerks since you are not having to directly inject polled input every so many frames but instead can just inject input events as they happen. Here is the code for this version, which has slightly more implemented than the Version 1 #include <engine.h> #include <windows.h> #include <SFML/Graphics/RenderWindow.hpp> #include <SFML/Graphics/String.hpp> #include <CEGUI.h> #include <CEGUIDefaultResourceProvider.h> #include <RendererModules/OpenGL/CEGUIOpenGLRenderer.h> #include <scriptingModules/LuaScriptModule/CEGUILua.h> #include <lua.hpp> std::map<sf::Key::Code, CEGUI::Key::Scan> mKeyMap; std::map<sf::Mouse::Button, CEGUI::MouseButton> mMouseButtonMap; void SetupEvents() { mKeyMap[sf::Key::Escape] = CEGUI::Key::Escape;mKeyMap[sf::Key::Num1] = CEGUI::Key::One; mKeyMap[sf::Key::Num2] = CEGUI::Key::Two;mKeyMap[sf::Key::Num3] = CEGUI::Key::Three; mKeyMap[sf::Key::Num4] = CEGUI::Key::Four;mKeyMap[sf::Key::Num5] = CEGUI::Key::Five; mKeyMap[sf::Key::Num6] = CEGUI::Key::Six;mKeyMap[sf::Key::Num7] = CEGUI::Key::Seven; mKeyMap[sf::Key::Num8] = CEGUI::Key::Eight;mKeyMap[sf::Key::Num9] = CEGUI::Key::Nine; mKeyMap[sf::Key::Num0] = CEGUI::Key::Zero;mKeyMap[sf::Key::Dash] = CEGUI::Key::Minus; mKeyMap[sf::Key::Equal] = CEGUI::Key::Equals;mKeyMap[sf::Key::Back] = CEGUI::Key::Backspace; mKeyMap[sf::Key::Tab] = CEGUI::Key::Tab;mKeyMap[sf::Key::Q] = CEGUI::Key::Q; mKeyMap[sf::Key::W] = CEGUI::Key::W;mKeyMap[sf::Key::E] = CEGUI::Key::E; mKeyMap[sf::Key::R] = CEGUI::Key::R;mKeyMap[sf::Key::T] = CEGUI::Key::T; mKeyMap[sf::Key::Y] = CEGUI::Key::Y;mKeyMap[sf::Key::U] = CEGUI::Key::U; mKeyMap[sf::Key::I] = CEGUI::Key::I;mKeyMap[sf::Key:] = CEGUI::Key:; mKeyMap[sf::Key:] = CEGUI::Key:;mKeyMap[sf::Key::LBracket] = CEGUI::Key::LeftBracket; mKeyMap[sf::Key::RBracket] = CEGUI::Key::RightBracket;mKeyMap[sf::Key::Return] = CEGUI::Key::Return; mKeyMap[sf::Key::LControl] = CEGUI::Key::LeftControl;mKeyMap[sf::Key::A] = CEGUI::Key::A; mKeyMap[sf::Key::S] = CEGUI::Key::S;mKeyMap[sf::Key:] = CEGUI::Key:; mKeyMap[sf::Key::F] = CEGUI::Key::F;mKeyMap[sf::Key::G] = CEGUI::Key::G; mKeyMap[sf::Key::H] = CEGUI::Key::H;mKeyMap[sf::Key::J] = CEGUI::Key::J; mKeyMap[sf::Key::K] = CEGUI::Key::K;mKeyMap[sf::Key::L] = CEGUI::Key::L; mKeyMap[sf::Key::SemiColon] = CEGUI::Key::Semicolon;mKeyMap[sf::Key::LShift] = CEGUI::Key::LeftShift; mKeyMap[sf::Key::BackSlash] = CEGUI::Key::Backslash;mKeyMap[sf::Key::Z] = CEGUI::Key::Z; mKeyMap[sf::Key::X] = CEGUI::Key::X;mKeyMap[sf::Key::C] = CEGUI::Key::C; mKeyMap[sf::Key::V] = CEGUI::Key::V;mKeyMap[sf::Key::B] = CEGUI::Key::B; mKeyMap[sf::Key::N] = CEGUI::Key::N;mKeyMap[sf::Key::M] = CEGUI::Key::M; mKeyMap[sf::Key::Comma] = CEGUI::Key::Comma;mKeyMap[sf::Key::Period] = CEGUI::Key::Period; mKeyMap[sf::Key::Slash] = CEGUI::Key::Slash;mKeyMap[sf::Key::RShift] = CEGUI::Key::RightShift; mKeyMap[sf::Key::Multiply] = CEGUI::Key::Multiply;mKeyMap[sf::Key::LAlt] = CEGUI::Key::LeftAlt; mKeyMap[sf::Key::Space] = CEGUI::Key::Space;mKeyMap[sf::Key::F1] = CEGUI::Key::F1; mKeyMap[sf::Key::F2] = CEGUI::Key::F2;mKeyMap[sf::Key::F3] = CEGUI::Key::F3; mKeyMap[sf::Key::F4] = CEGUI::Key::F4;mKeyMap[sf::Key::F5] = CEGUI::Key::F5; mKeyMap[sf::Key::F6] = CEGUI::Key::F6;mKeyMap[sf::Key::F7] = CEGUI::Key::F7; mKeyMap[sf::Key::F8] = CEGUI::Key::F8;mKeyMap[sf::Key::F9] = CEGUI::Key::F9; mKeyMap[sf::Key::F10] = CEGUI::Key::F10;mKeyMap[sf::Key::Numpad7] = CEGUI::Key::Numpad7; mKeyMap[sf::Key::Numpad8] = CEGUI::Key::Numpad8;mKeyMap[sf::Key::Numpad9] = CEGUI::Key::Numpad9; mKeyMap[sf::Key::Subtract] = CEGUI::Key::Subtract;mKeyMap[sf::Key::Numpad4] = CEGUI::Key::Numpad4; mKeyMap[sf::Key::Numpad5] = CEGUI::Key::Numpad5;mKeyMap[sf::Key::Numpad6] = CEGUI::Key::Numpad6; mKeyMap[sf::Key::Add] = CEGUI::Key::Add;mKeyMap[sf::Key::Numpad1] = CEGUI::Key::Numpad1; mKeyMap[sf::Key::Numpad2] = CEGUI::Key::Numpad2;mKeyMap[sf::Key::Numpad3] = CEGUI::Key::Numpad3; mKeyMap[sf::Key::Numpad0] = CEGUI::Key::Numpad0;mKeyMap[sf::Key::F11] = CEGUI::Key::F11; mKeyMap[sf::Key::F12] = CEGUI::Key::F12;mKeyMap[sf::Key::F13] = CEGUI::Key::F13; mKeyMap[sf::Key::F14] = CEGUI::Key::F14;mKeyMap[sf::Key::F15] = CEGUI::Key::F15; mKeyMap[sf::Key::RControl] = CEGUI::Key::RightControl;mKeyMap[sf::Key::Divide] = CEGUI::Key::Divide; mKeyMap[sf::Key::RAlt] = CEGUI::Key::RightAlt;mKeyMap[sf::Key::Pause] = CEGUI::Key::Pause; mKeyMap[sf::Key::Home] = CEGUI::Key::Home;mKeyMap[sf::Key::Up] = CEGUI::Key::ArrowUp; mKeyMap[sf::Key::PageUp] = CEGUI::Key::PageUp;mKeyMap[sf::Key::Left] = CEGUI::Key::ArrowLeft; mKeyMap[sf::Key::Right] = CEGUI::Key::ArrowRight;mKeyMap[sf::Key::End] = CEGUI::Key::End; mKeyMap[sf::Key::Down] = CEGUI::Key::ArrowDown;mKeyMap[sf::Key::PageDown] = CEGUI::Key::PageDown; mKeyMap[sf::Key::Insert] = CEGUI::Key::Insert;mKeyMap[sf::Key::Delete] = CEGUI::Key::Delete; mMouseButtonMap[sf::Mouse::Left] = CEGUI::LeftButton; mMouseButtonMap[sf::Mouse::Middle] = CEGUI::MiddleButton; mMouseButtonMap[sf::Mouse::Right] = CEGUI::RightButton; mMouseButtonMap[sf::Mouse::XButton1] = CEGUI::X1Button; mMouseButtonMap[sf::Mouse::XButton2] = CEGUI::X2Button; } CEGUI::Key::Scan toCEGUIKey(sf::Key::Code Code) { if (mKeyMap.find(Code) == mKeyMap.end()) { return (CEGUI::Key::Scan)0; } return mKeyMap[Code]; } CEGUI::MouseButton toCEGUIMouseButton(sf::Mouse::Button Button) { if (mMouseButtonMap.find(Button) == mMouseButtonMap.end()) { return (CEGUI::MouseButton)0; } return mMouseButtonMap[button]; } int main(int argc, char *argv[]) { lua_State * lua = lua_open(); luaL_openlibs(lua); SetupEvents(); bool gDone = false; // Setup Leadwerks Initialize(); // Use the CRC of the string "Leadwerks" to set a unique title SetAppTitle("258BD7E6"); // Setup the graphics based on the size of the control Graphics(1024, 768); // Get the rendering context from Leadwerks HGLRC leContext = wglGetCurrentContext(); // We will use CEGUI to handle the mouse HideMouse(); // Now we can find our application's window handle based on the class and title HWND leWnd = FindWindowA("BlitzMax GLGraphics", "258BD7E6"); // And we can rename it, so we can still run other instances of this application SetWindowTextA(leWnd, "Leadwerks + SFML + CEGUI"); // Create the main window sf::WindowSettings params; params.UserContext = PtrToUint(leContext); params.OverrideMsgs = true; sf::RenderWindow gWindow(leWnd, params); // Limit to 60 frames per second gWindow.UseVerticalSync(true); gWindow.SetFramerateLimit(60); CEGUI::OpenGLRenderer & renderer = CEGUI::OpenGLRenderer::create(); CEGUI::System & sys = CEGUI::System::create(renderer); CEGUI::LuaScriptModule & script_module = CEGUI::LuaScriptModule::create(lua); sys.setScriptingModule(&script_module); CEGUI::DefaultResourceProvider* rp = static_cast<CEGUI::DefaultResourceProvider*>(CEGUI::System::getSingleton().getResourceProvider()); const char* dataPathPrefix = "datafiles"; char resourcePath[MAX_PATH]; _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "schemes/"); rp->setResourceGroupDirectory("schemes", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "imagesets/"); rp->setResourceGroupDirectory("imagesets", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "fonts/"); rp->setResourceGroupDirectory("fonts", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "layouts/"); rp->setResourceGroupDirectory("layouts", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "looknfeel/"); rp->setResourceGroupDirectory("looknfeels", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "lua_scripts/"); rp->setResourceGroupDirectory("lua_scripts", resourcePath); _snprintf(resourcePath, MAX_PATH - 1, "%s/%s", dataPathPrefix, "xml_schemas/"); rp->setResourceGroupDirectory("schemas", resourcePath); CEGUI::Imageset::setDefaultResourceGroup("imagesets"); CEGUI::Font::setDefaultResourceGroup("fonts"); CEGUI::Scheme::setDefaultResourceGroup("schemes"); CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels"); CEGUI::WindowManager::setDefaultResourceGroup("layouts"); CEGUI::ScriptModule::setDefaultResourceGroup("lua_scripts"); CEGUI::XMLParser* parser = CEGUI::System::getSingleton().getXMLParser(); if(parser->isPropertyPresent("SchemaDefaultResourceGroup")) parser->setProperty("SchemaDefaultResourceGroup", "schemas"); script_module.executeScriptFile("demo8.lua", ""); // Setup Leadwerks with some basic stuff TWorld world = CreateWorld(); TEntity camera = CreateCamera(); PositionEntity(camera, Vec3(0, 2, -5)); TEntity light = CreateDirectionalLight(); RotateEntity(light, Vec3(45, 45, 0)); TBuffer buffer = CreateBuffer(GraphicsWidth(), GraphicsHeight(), BUFFER_COLOR | BUFFER_DEPTH | BUFFER_NORMAL); TEntity mesh = CreateCube(); PositionEntity(mesh, Vec3(0, 2, 0)); TVec3 camrotation = Vec3(0); float mx = 0; float my = 0; float move = 0; float strafe = 0; CEGUI::MouseCursor * cursor = CEGUI::MouseCursor::getSingletonPtr(); cursor->show(); bool bMouseDown = false; sf::Event Event; while(!gDone) { while(gWindow.GetEvent(Event)) { switch(Event.Type) { case sf::Event::TextEntered: CEGUI::System::getSingleton().injectChar(Event.Text.Unicode); break; case sf::Event::KeyPressed: CEGUI::System::getSingleton().injectKeyDown(toCEGUIKey(Event.Key.Code)); break; case sf::Event::KeyReleased: CEGUI::System::getSingleton().injectKeyUp(toCEGUIKey(Event.Key.Code)); break; case sf::Event::MouseMoved: CEGUI::System::getSingleton().injectMousePosition(static_cast<float>(Event.MouseMove.X), static_cast<float>(Event.MouseMove.Y)); break; case sf::Event::MouseButtonPressed: CEGUI::System::getSingleton().injectMouseButtonDown(toCEGUIMouseButton(Event.MouseButton.Button)); break; case sf::Event::MouseButtonReleased: CEGUI::System::getSingleton().injectMouseButtonUp(toCEGUIMouseButton(Event.MouseButton.Button)); break; case sf::Event::MouseWheelMoved: CEGUI::System::getSingleton().injectMouseWheelChange(static_cast<float>(Event.MouseWheel.Delta)); break; case sf::Event::Closed: gDone = true; break; } } gWindow.SetActive(); TurnEntity(mesh); UpdateAppTime(); UpdateWorld(); SetBuffer(buffer); RenderWorld(); SetBuffer(BackBuffer()); RenderLights(buffer); glPushAttrib(GL_ALL_ATTRIB_BITS); DrawText(0, 0, "Press F1 to bring back the GUI window."); glPopAttrib(); CEGUI::System::getSingleton().renderGUI(); gWindow.Display(); } sys.setScriptingModule(0); CEGUI::LuaScriptModule::destroy(script_module); CEGUI::System::destroy(); CEGUI::OpenGLRenderer::destroy(renderer); lua_close(lua); lua = 0; Terminate(); return EXIT_SUCCESS; } There are other ways to do this, but I just liked SFML by preference. You could probably write some quick Win32 code to take care of the input issues with Version 1 and have less dependencies and all, but I had already worked out how to modify SFML to work with Leadwerks, so I just stuck with that into looking into other alternatives. You;d probably just subclass the main window's WndProc and add the code to inject the input events as they happened rather than injecting the polled input. This used to bother me a lot as well. I always wanted just one 'thing' to deal with rather than over 9000 little things. However, over time I've come to realize that project organization far trumps all of the little issues of having too many dependencies or packages to work with in a project. By not trying to combine everything or consolidate into one package, you make updating a lot easier on yourself. In addition, your work is more usable by people who want complete control over how things work or have specific needs of their dependences. The most common example involves Lua. Ogre3D uses Lua, CEGUI uses Lua, and your own project might use Lua. That's three (!) different Lua packages in one project. That's a nightmare in itself due to Lua interop issues across different versions due to whatever reasons. As a result, your project needs to use one base Lua and share it across all three projects. Good libraries are setup so this is possible. You can just take the sources of those libraries and rip out the Lua and point them to your common one. If instead Ogre, CEGUI, and your own project all came as one consolidated lib, then you'd have a real problem on hand. If the Lua versions are so different that they can't all work together in compiled form, then you'd need to get the source and recompile it all, causing more work to be done. Now to get to the point: project organization. Let's say if everyone started to create their projects in a consistent meaningful way. For Visual Studio, this would be to create a new Solution Workspace and then adding your game project as a new project to it. Then, you have a Common folder that contains all your 3rd party stuff. A Bin folder is for executing and contains the DLLs and assets as needed. This would look like this: Inside your Common folder, you have 3rd party libraries setup usually in a form of a folder having an "include" and a "lib" if they are precompiled. Now it's just a matter of setting up each project in your solution to point to the directories. You could do it once via the Global settings in VS, but I prefer not doing that due to possible compatibility issues across different projects that use similar libraries but different versions. For each 3rd party component you are going to be: 1. Adding the Include directory to the project 2. Adding the Lib directory to the project 3. Adding any source files to the project folders 4. Linking the libs via project settings (no pragmas!) 5. Adding the header files to the project folders (makes life easier on Intellisense) Honestly, we're looking at no more than about 5 minutes of work max per 3rd party component we add. We only have to do this once per a project and then we can copy paste as needed. It can be a little tedious, I mean this is what my library settings line looks like: ndd.lib lua_d.lib sfml-audio-s-d.lib sfml-graphics-s-d.lib sfml-main-d.lib sfml-network-s-d.lib sfml-system-s-d.lib sfml-window-s-d.lib opengl32.lib glu32.lib CEGUIBase_d.lib CEGUICoronaImageCodec_d.lib CEGUIDevILImageCodec_d.lib CEGUIDirect3D9Renderer_d.lib CEGUIDirect3D10Renderer_d.lib CEGUIExpatParser_d.lib CEGUIFalagardWRBase_d.lib CEGUIFreeImageImageCodec_d.lib CEGUIIrrlichtRenderer_d.lib CEGUILuaScriptModule_d.lib CEGUIOgreRenderer_d.lib CEGUIOpenGLRenderer_d.lib CEGUISILLYImageCodec_d.lib CEGUITGAImageCodec_d.lib CEGUITinyXMLParser_d.lib CEGUIXercesParser_d.lib tolua++_d.lib And slightly easier, this is my includes: ../common/cegui/include;../common/leadwerks;../common/lua/include;../common/nd/include;../Common/sfml/include But let's consider what we gain: 1. Easy redeployment: We can compress a library in Common and send it to someone else. All they do is put it in their Common and then update project settings with the paths. They don't even have to do this themselves since they can just copy/paste the lines needed. If you look at my includes, you'll see they are relative paths rather than absolute, so anyone can use them as long as they are using a similar project setup. 2. Easy Updating: Let's say we want to migrate to a new version of a library. Rename the folder in Common and use the new one. If things go bad, it's just a matter of renaming again. If we need to share one common library across other libraries, this is easy as well since it's already part of the project path. And lets consider the costs: 1. File Bloat: Meh, just part of the territory when using C++. So if anything, I'm going to recommend to you that you don't spend your time on trying to work against the bloated design of some of these OSS libraries and instead just work with it. By setting up a common project organization such as I described above, there's no real need to muck around with the sources and cripple yourself when patches come around. Instead, you can just concentrate on your wrapper for everything that makes use of any format of the library, whether a user wants to use it as a DLL or as a static library. I think that'd ensure that your work goes a lot further and lasts a lot longer than otherwise. Just my thoughts on the matter though, good luck with your project!
×
×
  • Create New...