sorry, ik kon geen betert topictitel verzinnen
(suggesties welkom)
ik ben mn game engine aan het ombouwen voor multithreading support, maar loop tegen een designprobleem aan. mn huidige opzet 'werkt', maar de dataoverdracht tussen threads behoeft me wat al teveel code. bovendien gok ik dat er mooiere oplossingen zijn (maar ik kan ze niet vinden).
ook zou ik het datagebeuren iets mooier willen.
dit is in semi-pseudocode de huidige opzet:
<toelichting>
eerst even een uitleg om het lezen wat makkelijker te maken..
een Thread heeft een MessageQueue die hij in de 'main thread loop', operator()(), polled op nieuwe berichten.
een voorbeeld: stel er is een nieuw bericht (in MessageData.message) voor de graphics thread: 'e_GraphicsThreadMessage_CreateContext'..
de CreateContext functie in die thread leest message->messagedata->parameters uit voor bijv de resolutie en hoeveel BPP.
hij doet zn ding en zet in message->messagedata->returnvalues->values een boolean of het al dan niet is gelukt.
vervolgens roept hij message->messagedata->returnvalues->ready() aan.
daardoor wordt in returnvalues de isReady bool op true gezet.
de aanroepende thread is al die tijd diezelfde isReady aan het pollen (via WaitForReady());
die leest uit message->messagedata->returnvalues->values de boolean uit en weet zo of de CreateContext is geslaagd.
</toelichting>
hier een diagram van dit gebeuren, dat maakt het wellicht wat duidelijker..
(let niet op mn UML syntax, die is ongetwijfeld onjuist
maar t gaat om het idee)

dan nu het probleem: zo zien de uitvoerende functie en de ontvangende functie er uit: (= veel te veel code)
probleem 1:
als ik nu voor elke functie die ik ga aanroepen in een andere thread zoveel moet typen word ik gestoord
probleem 2:
bovendien moeten de returnvalues ge'new'ed worden in de verwerkende thread, maar gedelete in de aanroepende thread. dat lijkt me niet netjes.. maarja, de returnvalue kan pas weg als de aanroepende thread em heeft gelezen, maar de verwerkende thread weet niet wanneer dat is, dus die kan em niet deleten. het moet wel een *pointer zijn, omdat ik em - naar mijn weten - anders niet kan static_cast'en naar het juiste type.
probleem 3:
de static_casts... op zich moet dat kunnen - ik moet me gewoon braaf houden aan de parameters/return values van de functie in de verwerkende thread. toch zou het fijn zijn als die casts ergens netjes werden verstopt zodat ik daar kan checken op het typeID en een mooie error kan geven als ik een fout type gebruik.
probleem 4:
ik moet voor elk type data een nieuwe class maken, ondanks dat er verder weinig gebeurt in die classes.
probleem 5:
ik heb uberhaupt een sterk gevoel dat dit een stuk beter kan. mijn sterke gevoelens zijn over het algemeen gegrond
voorwaarde:
datatypen moeten een vast 'id' hebben zoals met mn enumlijstje. dit omdat ik de data uiteindelijk ook wil kunnen saven en weer loaden - dan moeten alle datatypes dus hetzelfde id hebben en niet ineens verschoven zijn.
In <dit> topic laat .oisyn een oplossing zien voor een ander probleem, maar ik heb het vermoeden dat iets soortgelijks (met templates oid) hier wellicht ook van toepassing is. alleen werkt dat met een dynamisch enum - mijn voorwaarde is dus dat elk datatype een vast id heeft, dus dat wordt - lijkt me - lastig.
ik hoop dat het allemaal een beetje duidelijk is, elke suggestie is welkom! vragen ter verduidelijking ook, tis voor mij natuurlijk allemaal duidelijk maar het begrijpen van code van anderen is natuurlijk een stuk lastiger..
ik ben mn game engine aan het ombouwen voor multithreading support, maar loop tegen een designprobleem aan. mn huidige opzet 'werkt', maar de dataoverdracht tussen threads behoeft me wat al teveel code. bovendien gok ik dat er mooiere oplossingen zijn (maar ik kan ze niet vinden).
ook zou ik het datagebeuren iets mooier willen.
dit is in semi-pseudocode de huidige opzet:
<toelichting>
eerst even een uitleg om het lezen wat makkelijker te maken..
een Thread heeft een MessageQueue die hij in de 'main thread loop', operator()(), polled op nieuwe berichten.
een voorbeeld: stel er is een nieuw bericht (in MessageData.message) voor de graphics thread: 'e_GraphicsThreadMessage_CreateContext'..
de CreateContext functie in die thread leest message->messagedata->parameters uit voor bijv de resolutie en hoeveel BPP.
hij doet zn ding en zet in message->messagedata->returnvalues->values een boolean of het al dan niet is gelukt.
vervolgens roept hij message->messagedata->returnvalues->ready() aan.
daardoor wordt in returnvalues de isReady bool op true gezet.
de aanroepende thread is al die tijd diezelfde isReady aan het pollen (via WaitForReady());
die leest uit message->messagedata->returnvalues->values de boolean uit en weet zo of de CreateContext is geslaagd.
</toelichting>
C++:
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
51
52
53
54
55
56
57
58
| // data gedeelte enum e_DataType { e_DataType_Integer = 1, e_DataType_Boolean = 2, e_DataType_Float = 3, ... etcetera ... }; class Data { Data(e_DataType dataType) : dataType(dataType); e_DataType dataType; }; class BooleanData : public Data { BooleanData(bool value) : Data(e_DataType_Boolean) { data = value; } bool data; }; class IntegerData : public Data { IntegerData(int value) : Data(e_DataType_Integer) { data = value; } int data; }; .. etcetera .. // functionele gedeelte class DataArray { std::list<Data*> elements; }; class ReturnValues { void WaitForReturn(); // called from sender: poll if receiver is ready void Ready(); // called from receiver when message has been processed bool isReady; DataArray data; }; struct FunctionData { DataArray parameters; ReturnValues returnValues; }; struct MessageData { MessageType message; FunctionData *functionData; }; class MessageQueue { std::list<MessageData> queue; }; class Thread { virtual operator()() = 0; MessageQueue messageQueue; }; |
hier een diagram van dit gebeuren, dat maakt het wellicht wat duidelijker..
(let niet op mn UML syntax, die is ongetwijfeld onjuist

dan nu het probleem: zo zien de uitvoerende functie en de ontvangende functie er uit: (= veel te veel code)
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // de functie in de thread die de 'CreateContext' opdracht geeft aan de graphics thread: FunctionData *fdata = new FunctionData(); IntegerData *Iwidth = new IntegerData(width); IntegerData *Iheight = new IntegerData(height); IntegerData *Ibpp = new IntegerData(bpp); fdata->parameters.PushElement(Iwidth); fdata->parameters.PushElement(Iheight); fdata->parameters.PushElement(Ibpp); renderer3DTask->messageQueue.PushMessage(e_Renderer3DThreadMessage_CreateContext, fdata); fdata->returnValues.WaitForReturn(); BooleanData *success = static_cast<BooleanData*>(fdata->returnValues.data.PopElement()); if (!success->data.data) { Log(e_FatalError, "GraphicsSystem", "Initialize", "Could not create context"); } else { Log(e_Notice, "GraphicsSystem", "Initialize", "Created context, resolution " + int_to_str(width) + " * " + int_to_str(height) + " @ " + int_to_str(bpp) + " bpp"); } delete success; delete Iwidth, Iheight, Ibpp; delete fdata; |
C++:
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
| // de graphicsthread die de message gaat verwerken.. FunctionData *fdata = messageQueue.WaitForMessage(message); switch (message) { case e_Renderer3DThreadMessage_CreateContext: { // parameters: // IntegerData width // IntegerData height // IntegerData bpp // // return values: // BooleanData success assert(fdata); int width = static_cast<IntegerData*>(fdata->parameters.PopElement())->data.data; int height = static_cast<IntegerData*>(fdata->parameters.PopElement())->data.data; int bpp = static_cast<IntegerData*>(fdata->parameters.PopElement())->data.data; bool success = CreateContext(width, height, bpp); BooleanData *Bsuccess = new BooleanData(success); fdata->returnValues.data.PushElement(Bsuccess); } break; if (fdata) fdata->returnValues.Ready(); } |
probleem 1:
als ik nu voor elke functie die ik ga aanroepen in een andere thread zoveel moet typen word ik gestoord
probleem 2:
bovendien moeten de returnvalues ge'new'ed worden in de verwerkende thread, maar gedelete in de aanroepende thread. dat lijkt me niet netjes.. maarja, de returnvalue kan pas weg als de aanroepende thread em heeft gelezen, maar de verwerkende thread weet niet wanneer dat is, dus die kan em niet deleten. het moet wel een *pointer zijn, omdat ik em - naar mijn weten - anders niet kan static_cast'en naar het juiste type.
probleem 3:
de static_casts... op zich moet dat kunnen - ik moet me gewoon braaf houden aan de parameters/return values van de functie in de verwerkende thread. toch zou het fijn zijn als die casts ergens netjes werden verstopt zodat ik daar kan checken op het typeID en een mooie error kan geven als ik een fout type gebruik.
probleem 4:
ik moet voor elk type data een nieuwe class maken, ondanks dat er verder weinig gebeurt in die classes.
probleem 5:
ik heb uberhaupt een sterk gevoel dat dit een stuk beter kan. mijn sterke gevoelens zijn over het algemeen gegrond
voorwaarde:
datatypen moeten een vast 'id' hebben zoals met mn enumlijstje. dit omdat ik de data uiteindelijk ook wil kunnen saven en weer loaden - dan moeten alle datatypes dus hetzelfde id hebben en niet ineens verschoven zijn.
In <dit> topic laat .oisyn een oplossing zien voor een ander probleem, maar ik heb het vermoeden dat iets soortgelijks (met templates oid) hier wellicht ook van toepassing is. alleen werkt dat met een dynamisch enum - mijn voorwaarde is dus dat elk datatype een vast id heeft, dus dat wordt - lijkt me - lastig.
ik hoop dat het allemaal een beetje duidelijk is, elke suggestie is welkom! vragen ter verduidelijking ook, tis voor mij natuurlijk allemaal duidelijk maar het begrijpen van code van anderen is natuurlijk een stuk lastiger..