How to get around cyclic referencing?!?

BlitzMax Forums/BlitzMax Programming/How to get around cyclic referencing?!?

DH(Posted 2009) [#1]
With Max's lack of multi-pass compiling, I know I cannot over burden the compiler with cyclic referencing, however I enjoy using callbacks to do work between object instances. How can I accomplish callbacks without cyclic referencing?

Example:
This is used widely in threading unless all the objects needed were created by the thread in question, in which case is only 1 of many uses of threading. Many times multithreading, one thread creates an object and passes it to another thread for work done on it. In my case, I need an object created by one thread, worked on by another (just a portion of the object like it's TCP stack), and any data generated by it to be passed back to the first thread for processing when the first thread sees fit to do so. The object must live in both threads, which isn't the problem, but doing call backs on it with regards to the compile and referencing seems to render an efficient solution useless.

"ObjectA.bmx"
Import "ObjectB.bmx"
Import "ObjectC.bmx"
Type ObjectA
	Field MightDoWorkWithB:ObjectB
	Field Commonworker:ObjectC
End Type


"ObjectB.bmx"
Import "ObjectA.bmx"
Import "ObjectC.bmx"
Type ObjectB
	Field MightDoWorkWithA:ObjectA
	Field Commonworker:ObjectC
End Type


"ObjectC.bmx"
Import "ObjectA.bmx"
Import "ObjectB.bmx"
'Common object used by both A and B, Can be created by A or B, work it does it communicates back to whoever created it
Type ObjectC
	Field callBackToA:ObjectA
	Field callBackToB:ObjectB
End Type


I know the one answer to the question is "just put it all in one file". But with the length of what these objects do, it would quickly deem the project unmanageable (and totally counter-productive to the objective behind having imports)

If I compiled this, it would scream duplicate definitions. But if I dont have it just as it is, it would claim it can't find definitions. Other languages use multipass-compiling (first pass to flag definitions that are needed for compiling by the second pass) or declarations saying that a definition will be included in a later part of the compile or even holding the compiled definition with conjunction to the defined file for use when compiling all modules on the tree where needed. How can I get around this and still use the callbacks?


Gabriel(Posted 2009) [#2]
One common way to solve these kind of A talks to B and B talks to A problems is to use a Messaging system. If you're into design patterns, there are probably a few to look at. If not, it works something like this:

Currently, you have two objects. Object A and Object B. Object A has a function which is called when Object B does something. Here's what we do instead:

Object A tells the messaging system that it wants to register as a listener for events of a certain type ( from Object B if the event is Object specific).

Object B does that event at some point. It sends a message to the messaging system telling the messaging system what happened.

The messaging system looks through the list of receivers for that sort of event (and that object if the event is specific to an object) and sends a message to all of them, telling them that the event took place.

So at this point, Object A gets a message from the Messaging system telling it about the event that happened. It now calls the appropriate function to deal with that response.



Typically you would implement this with a base class with certain register, and receive methods (possibly abstract) and then have all your game objects inherit from this base class. Then you can override the receive method with a method which deals with the received events and the messaging system only has to know about the base object.

So your Messaging system needs to know about the base object. The base object doesn't need to know about anything but itself, and all your other objects are (theoretically) self sufficient.

Since you have circular refs, you will presumably want Object B to listen for an event Object A does, so you would just have that register as a listener too.


DH(Posted 2009) [#3]
That'll work! Thanks Gabriel!


Arowx(Posted 2009) [#4]
Another way is to use a hub or core include/import file that ensures all elements are included!

Include effectivly creates a larger file at the cost of decreased compile speed (not really an issue) so if you have a set of types that cyclicly refer just ensure that they are included into a single file that can then be combined with other code files!


DH(Posted 2009) [#5]
Another way is to use a hub or core include/import file that ensures all elements are included!

Unfortunately this doesn't work when your using the "framework" stuff as the compiler doesn't carry down references into each import, it's like it starts the compile slate clean with every import it goes into.


TaskMaster(Posted 2009) [#6]
Is this allowed in any language?

I think you just have a design flaw...


DH(Posted 2009) [#7]
What? Callbacks/Delegates?


TaskMaster(Posted 2009) [#8]
Cyclic references as you call it, the subject of your post...


ImaginaryHuman(Posted 2009) [#9]
The threaded garbage collector handles cyclic references ... or is that something different?


JoshK(Posted 2009) [#10]
Import "ObjectB.bmx"
Import "ObjectC.bmx"

Type ObjectA
	Field MightDoWorkWithB:ObjectB
	Field Commonworker:ObjectC

Method Free()
Commonworker.MightDoWorkWithA=Null
Commonworker=Null
MightDoWorkWithB.MightDoWorkWithA=Null
MightDoWorkWithB=Null
EndMethod

End Type



TaskMaster(Posted 2009) [#11]
He wants to be able to do this:

A imports and uses B
B imports and uses A

So, each one depends on the other to compile, but since neither one of them can be compiled first, he has a chicken and egg scenario and it won't work.

It is a design flaw, IMHO.


DH(Posted 2009) [#12]
What other compilers suffer the "chicken and egg" scenario?


ImaginaryHuman(Posted 2009) [#13]
Oh. Use include. ;-P


Muttley(Posted 2009) [#14]
@Dark Half: Well, C++ for a start. Hence the pre-processor ifndef/define/endif stuff you get in the header files...


Jur(Posted 2009) [#15]
Maybe it is a design flaw but it forces me to to maintain a good structure for my code :)


DH(Posted 2009) [#16]
@Dark Half: Well, C++ for a start. Hence the pre-processor ifndef/define/endif stuff you get in the header files...


But isn't the c++ pre-processor part of the implementation of c++? I thought all c++ compilers came with the pre-processor.

Oh. Use include. ;-P

How would include be used? Is it the same as import?

Perhaps I'll just use a combination of inheritance and uncasted objects as a hacky way to do callbacks. I was toying with what Gabriel had said however with regards to speed I can't afford to iterate through a list of objects, the messages need to be instantaneous hence the need for a direct link to the object.

As for design flaw, are you referring to my design? Although I can honestly say it isn't exactly considered to be on the "good practice" list, it is still a common practice. However if your referring to it being a design flaw with regards to the limitations of Blitzmax, then yeah, it is however with what I am currently doing it's a design flaw I was hoping would work.... This project's counter-part is in c# (more specifically in Unity using c#).


ziggy(Posted 2009) [#17]
Include -to make it easier to understand- just puts a file inside another.

If FileA includes FileB, any type declared on FileA can reference FileB types, and any type defined in FileB can reference FilaA types, as efectivily, they are the same file after the include is calculated at compile time.

Some people here will tell you that any file in a program has to be imported (becouse using include will force a rebuild in case any of the included files is modified). Others will recommend you to ignore import and use only include becouse it allows you to organize your code structure as you wish...

I would recommend you to organize your code into compilation clusters, that is, every program is a collection of imports, but each import can be a single file or a collection of includes. Every import is a compilation cluster -or compilation module in .net talk- and should contain all classes involved on a specific task. That will help you a lot in code re-usability.


Nate the Great(Posted 2009) [#18]
so what if you did this

main.bmx imports a

a.bmx includes b which needs to reference a and viceversa


Dreamora(Posted 2009) [#19]
you can do that too, but with more and more things it will become less well maintainable than the cluster / package approach