Your Thoughts on My Library

Pages: 12
There is infinite recursion since I abolished the "fall-back pattern" of returning a state from a function and then passing it through a switch-case statement like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "ManyFunctions.hpp"

void logic()
{
    int (*fptr)() = someFunction;

    switch (fptr())
    {
        case 0:    fptr = anotherFunction;
        case 1:    fptr = thirdFunction;
        // More cases...
        default:    return;
    }
}


Why did I abolish this design pattern? Because it centralizes control and doesn't give me enough freedom, for it implies every function MUST return. We'd need to have global data to communicate between the different functions. I find that ugly and, well,.. globals, do I need to say any more? xD

Considering the amount of data each function may allocate on the stack, it may easily overflow.
Last edited on
Properly designed code doesn't need globals to communicate.

What you have is a way to use globals that look like it uses globals and also at the same time causes infinite recursion leading to an overflow of the stack.

Bourgond Aries wrote:
it centralizes control and doesn't give me enough freedom, for it implies every function MUST return.
What purpose do you give a function if it never lets you have your game back that it borrowed from you last year? It's like saying "hey go fetch the mail for me" and then your life just...stops.
Last edited on
L B wrote:
Properly designed code doesn't need globals to communicate.


I think that's opinion, and I agree, however...


L B wrote:
What you have is a way to use globals that look like it uses globals and also at the same time causes infinite recursion leading to an overflow of the stack.


Well the "globals" are not really global, only local to the chain of items that are connected to the same hash table, altho it feels almost as if they're globals. They're also dynamically "global", it's not decided at compile-time and that's it.

The stack overflow is averted by using new threads to clear the stack of the callee.


What purpose do you give a function if it never lets you have your game back that it borrowed from you last year? It's like saying "hey go fetch the mail for me" and then your life just...stops.


I think I don't quite get what you mean, you mean to say that the caller calls a function to do some work for the callee, but then just, dies?
If that's what you meant: that's not the use of this library, it's to give program or resource control to another class object: "I planted this apple tree for you, take delight in its fruits.". Then it just dies.
Bourgond Aries wrote:
Well the "globals" are not really global, only local to the chain of items that are connected to the same hash table, altho it feels almost as if they're globals. They're also dynamically "global", it's not decided at compile-time and that's it.
If you can access the variables from any point in your written code, you have globals. If you still don't believe me, then it's not my responsibility to argue this any further.

Bourgond Aries wrote:
The stack overflow is averted by using new threads to clear the stack of the callee.
This is the worst possible way to design a program. It's basically like keeping all your garbage and moving to a new house every time the old one gets full. Your program would be more efficient and less memory intensive with following the design patterns meant for C++. If you don't like the C++ design patterns, just used a different language.

For example, I'd recommend you look into a functional programming language, e.g. Lisp or Haskell. These languages encourage the things I am discouraging you from doing.

Bourgond Aries wrote:
it centralizes control and doesn't give me enough freedom, for it implies every function MUST return.
LB wrote:
What purpose do you give a function if it never lets you have your game back that it borrowed from you last year? It's like saying "hey go fetch the mail for me" and then your life just...stops.
Bourgond Aries wrote:
I think I don't quite get what you mean, you mean to say that the caller calls a function to do some work for the callee, but then just, dies?
No. The guy you sent to fetch your mail never comes back, and you just stand there waiting until a new Earth is built and the Earth you are on is destroyed, like when you create a new thread to clear the stack of the old one.


My point is, you are abusing C++ and using it in a less efficient way that it is not meant for. Plenty of successful, modular, well-designed programs have been made without any of the techniques you claim to use.

I'm still willing to believe that your technique might actually be good and that I am just misunderstanding something, but I don't have enough information for that. I would be interested to see a small example of a game or program that uses your technique of infinite recursion followed by creating a new thread to destroy the old one.
L B wrote:
If you can access the variables from any point in your written code, you have globals. If you still don't believe me, then it's not my responsibility to argue this any further.


Let's clear it up:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class C : cpm::Vault
{
public:
    C()
    {
        cpm::Vault_ptr<int> smt(fetch("val"));
        // can't access val from here.
        // entry not present, return nullptr
    }
    ~C()
    {
    }
};

class B : cpm::Vault
{
public:
    B()
    {
        cpm::Vault_ptr<int> ymu(fetch("val"));
        (*ymu)++;
        // val accessed from here
    }
    ~B()
    {
    }
};

class A : cpm::Vault
{
public:
    A()
    {
        cpm::Vault_ptr<int> val(new int);
        store(val, "val");
        build<B>();
        create<C, cpm::halt>();
        // val accessed from here
    }
    ~A()
    {
    }
};

int main(int argc, char **argv)
{
    cpm::Vault::create<A, cpm::halt>();
    // can't access val here
    return 0;
}


No globals according to your definition?



L B wrote:
This is the worst possible way to design a program. It's basically like keeping all your garbage and moving to a new house every time the old one gets full. Your program would be more efficient and less memory intensive with following the design patterns meant for C++. If you don't like the C++ design patterns, just used a different language.


No not in this case; I just get rid of all items used in a certain state, and enter a new state with new items. It doesn't "get full". Note that using Vault_ptr (which inherits from std::unique_ptr) deletes non stored data, and that the hash table deletes data when there are 0 references to it, and that the stack always deallocates the data when the class object goes out of scope. There are no memory leaks.



L B wrote:
No. The guy you sent to fetch your mail never comes back, and you just stand there waiting until a new Earth is built and the Earth you are on is destroyed, like when you create a new thread to clear the stack of the old one.


That's not at all the point of the library; which is to convey the different states. Calling a class is not meant to return. It's not a function calling library, it's a state conveying one. If you want functions you call a function, simple.

L B wrote:
I'm still willing to believe that your technique might actually be good and that I am just misunderstanding something, but I don't have enough information for that. I would be interested to see a small example of a game or program that uses your technique of infinite recursion followed by creating a new thread to destroy the old one.


Alright let me rewrite some of my code in my current project in pseudo-code:

1
2
3
4
5
6
7
8
#include "MainMenu.hpp"
#include <Corus/Corus.hpp> // cpm lib

int main(int argc, char **argv)
{
    cpm::create<MainMenu, cpm::halt | cpm::heap | cpm::toss>(); // New thread created, main thread pauses here.
    return 0;
}


So thread 2 enters the MainMenu object, thread 1 waits in the create function. I can't delete thread 1 because it must return the entire program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "MainMenu.hpp"
#include "Game1.hpp"

MainMenu::MainMenu()
:
ptr(fetch("this")),
wnd(fetch("that"))
{
    if (!ptr) // Evaluates to true
    {
        ptr = new Music;
        store(ptr, "this");
    }
    if (!wnd) // Evaluates to true
    {
        wnd = new Window(800, 600, "Muh Title");
        store(wnd, "that");
    }
    initDrawingThread();
    initAllOtherShit();
    eventHandler();
}


MainMenu is now active and running, drawing, waiting for events, basically handling its resources, confined to itself.
"New Game" is chosen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void MainMenu::eventHandler()
{
    while (running)
    {
        if (newGameSelect)
        {
            running = false; // Causes drawer to stop
            build<Game1>(); // New thread enters Game1(), current thread will exit eventHandler, exit ctor, destroy itself.
        }
        else if (wannaExitBro)
        {
            running = false; // This thread will exit, reference counter ticks 0, hash table deleted, contents deleted, main thread automatically resumed.
        }
    }
}



Now Game1 class can assume control. All data used in MainMenu is now destroyed, except items that went through store function.
We thus do not waste time destroying and newing something that's gonna be re-used anyway; fonts, rendering context, music manager, images, textures...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "Game1.hpp"

Game1::Game1()
:
ptr(fetch("this")),
wnd(fetch("that"))
{
    if (!ptr) // Will evaluate to false
    {
        ptr = new Music;
        store(ptr, "this");
    }
    if (!wnd) // Evaluates to false
    {
        wnd = new Window(800, 600, "Muh Title");
        store(wnd, "that");
    }
    initSomeData();
    wildlyDifferentFunctions();
    eventHandler();
}


Game1, when it's finished, can return to MainMenu, or perhaps start Game2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "Game2.hpp"
#include "MainMenu.hpp"

void Game1::eventHandler()
{
    while (running)
    {
        if (game2Select)
        {
            running = false; // Causes drawer to stop
            build<Game2>(); // New thread goes into Game2(), current thread exits eventHandler, leaves ctor, destroys self.
        }
        else if (menuSelect)
        {
            running = false;
            build<MainMenu>(); // New thread goes into MainMenu(), current thread exits eventHandler, leaves ctor, destroys self.
        }
        else
        {
            restartTheGame();
        }
    }
}



I can add endlessly many classes without much effort.
Bourgond Aries wrote:
No globals according to your definition?
You have coupling, which is just as bad as globals. What is worse is that the coupling is completely not obvious to someone using the classes.
Bourgond Aries wrote:
No not in this case; I just get rid of all items used in a certain state, and enter a new state with new items. It doesn't "get full". Note that using Vault_ptr (which inherits from std::unique_ptr) deletes non stored data, and that the hash table deletes data when there are 0 references to it, and that the stack always deallocates the data when the class object goes out of scope. There are no memory leaks.
Reread the text you quoted and assume that the house you leave is demolished/cleaned out after you leave.
Bourgond Aries wrote:
That's not at all the point of the library; which is to convey the different states. Calling a class is not meant to return. It's not a function calling library, it's a state conveying one. If you want functions you call a function, simple.
There is no need to "convey the different states"; I do not understand the point of your library.
Bourgond Aries wrote:
So thread 2 enters the MainMenu object, thread 1 waits in the create function. I can't delete thread 1 because it must return the entire program.
Why is the entire program not thread 2? I don't understand the purpose of creating a separate thread only to have the first thread wait for it to complete.
Bourgond Aries wrote:
4
5
6
7
MainMenu::MainMenu()
:
ptr(fetch("this")),
wnd(fetch("that"))
Why are "this" and "that" not passed as parameters to the constructor?
Bourgond Aries wrote:
Now Game1 class can assume control. All data used in MainMenu is now destroyed, except items that went through store function.
We thus do not waste time destroying and newing something that's gonna be re-used anyway; fonts, rendering context, music manager, images, textures...
This has the same effect as returning to main from a function call to draw the menu, and having main then call a function to draw the game. There is no reason to waste resources on creating and destroying threads or transmitting data through a hash table.
Bourgond Aries wrote:
8
9
10
11
12
13
14
15
16
17
    if (!ptr) // Will evaluate to false
    {
        ptr = new Music;
        store(ptr, "this");
    }
    if (!wnd) // Evaluates to false
    {
        wnd = new Window(800, 600, "Muh Title");
        store(wnd, "that");
    }
If they evaluate to false then why are they even there at all?
Bourgond Aries wrote:
Game1, when it's finished, can return to MainMenu, or perhaps start Game2:
I don't udnerstand why you have both "Game1" and "Game2". Also, this is once again a problem that is more easily solved by returning to main from the game function and having main then call the next function, except without all the overhead of creating and destroying threads and passing variables though hash tables.
Bourgond Aries wrote:
I can add endlessly many classes without much effort.
You seem to have gone through quite the effort to create the library and avoid normal parameter passing and flow control.


Here's my version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "Menu.hpp"
#include "Game.hpp"

int main(int argc, char **argv)
{
    Resource r = getResource();
    UserAction ua = UserAction::Menu; //an enum
    while(ua != UserAction::Exit)
    {
        switch(ua)
        {
        case UserAction::Menu:
            {
                Menu m (r); //r passed by reference
                ua = m.BlockUntilSelect();
            } break;
        case UserAction::Game:
            {
                Game g (r); //r passed by reference
                ua = g.BlockPlay();
            } break;
        }
    }

    return 0;
}
Done.
L B wrote:
You have coupling, which is just as bad as globals. What is worse is that the coupling is completely not obvious to someone using the classes.


Not really, see the code where it says // evaluates to false. If the items are not present, it'll create them themselves, in this situation, we simply know that the items will always be present.

L B wrote:
Why is the entire program not thread 2? I don't understand the purpose of creating a separate thread only to have the first thread wait for it to complete.


I considered that a while ago, but it's not feasible:
If I were to go directly into Class1, Class1 builds Class2, Class1 returns, then the main function returns. What happens when the main function returns (for me) is that the entire program exits abruptly.

L B wrote:
Why are "this" and "that" not passed as parameters to the constructor?


They're supposed to be fetched from the hash table so that any class may summon this class, when the items are not present, it initializes them by itself.

L B wrote:
This has the same effect as returning to main from a function call to draw the menu, and having main then call a function to draw the game. There is no reason to waste resources on creating and destroying threads or transmitting data through a hash table.


That's true, but I wouldn't call it a waste just yet.

L B wrote:
Here's my version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "Menu.hpp"
#include "Game.hpp"

int main(int argc, char **argv)
{
    Resource r = getResource();
    UserAction ua = UserAction::Menu; //an enum
    while(ua != UserAction::Exit)
    {
        switch(ua)
        {
        case UserAction::Menu:
            {
                Menu m (r); //r passed by reference
                ua = m.BlockUntilSelect();
            } break;
        case UserAction::Game:
            {
                Game g (r); //r passed by reference
                ua = g.BlockPlay();
            } break;
        }
    }

    return 0;
}

Done.


That's actually perfectly acceptable to me, but I'm being the perfectionist in making it possible to easily scale the code into any proportion. I can for example create virtually endlessly many "chains" of objects acting independently of others.
I do feel that using enums in a switch-case statement, and passing a resource (struct?) is quite good, I've actually done that before and I didn't like it because of the Resource struct gets too tangled with all kinds of information, after a while it's just like a global since you pass it to every instance explicitly. The case is also true for me, but I am at least able to create a new chain with a clean Resource struct on demand, not by coding it explicitly. I like such generic solution. I also do not send it explicitly, the created object just knows it exists, and it try to get its hands on data if available, else it'll create it by itself.

What I'm very uncomfortable with is the fact that you'd need to add an entry manually in the enum list for every new UserAction, whilst also being forced to add a case for every UserAction in the switch scope. My library automates that. I really dislike programming where for every time I create something, I need to add it to a list, add an entry here, another there... Maybe it comes down to personal tastes but I hope you at least get my point. I've spent quite a lot of time on design, and I've gone through most - if not all - possible solutions, your code being one of them.
Last edited on
I had hoped that from my enum example you would have realised how to abstract it to be more modular. Not only that I have no idea how you could need more than Menu, Game, and Exit. Anyway, here's a more modular version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <map>
#include <functional>
#include <typeinfo>
#include <cstdlib>

#include "MainMenu.hpp"
#include "Game.hpp"

int main()
{
    GameSurface gs;
    std::map<std::type_info, std::function<std::type_info (void)>> actions;
    actions[typeid(void)] = std::bind(std::exit, 0);
    MainMenu::Register(actions, gs); //registers itself and possibly others
    Game::Register(actions, gs); //both params passed by reference

    std::function<std::type_info (void)> action = actions[typeid(MainMenu)];
    while(true)
    {
        action = action();
    }
}
If this is not acceptable to you, I have no idea what you are looking for.
Topic archived. No new replies allowed.
Pages: 12