Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternatives to cyclic imports #55

Closed
marekmaskarinec opened this issue Mar 29, 2021 · 18 comments
Closed

Alternatives to cyclic imports #55

marekmaskarinec opened this issue Mar 29, 2021 · 18 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@marekmaskarinec
Copy link
Contributor

marekmaskarinec commented Mar 29, 2021

I have a function like this in file called game.um:

fn getdist*(x1, y1, x2, y2: int32): real

Then I try to call it in another file (they are both in the same directory):

if game.getdist(e.ent.p.x, e.ent.p.y, mage.p.x, mage.p.y) < 200 {

But I get this error: Unknown identifier getdist

I import game.um, so I don't know, why it doesn't work. Is it a problem, because game.um contains the main function? I normally call functions from other files.

@vtereshkov
Copy link
Owner

Will have a look on April 1-2

@marekmaskarinec
Copy link
Contributor Author

I put the function in another file, and it works now. Is it possible, that files with main function or files loaded by umkaInit can;t be used as a module?

@vtereshkov
Copy link
Owner

This is not an intended behavior. However, I cannot guarantee that this doesn't happen.

@vtereshkov vtereshkov added the bug Something isn't working label Mar 29, 2021
@marekmaskarinec
Copy link
Contributor Author

This applies to variable too. There are also some cases, when one specific file can't use functions from other files. Idk why.

@vtereshkov
Copy link
Owner

By the way, are you sure you don't have circular imports here?

@marekmaskarinec
Copy link
Contributor Author

It seems like I do. I'm probably just used to go. Are there solutions to this?

My source code is available here.
It tried to use enemy.enm in projectile.um.

@marekmaskarinec
Copy link
Contributor Author

@vtereshkov it it possible in umka, to have something like go, where all variables, functions and types are shared among one package (in this case folder). I think circular import is unavoidable, if I try to recreate this by just importing.

@vtereshkov
Copy link
Owner

@marekmaskarinec I think the problem is indeed with circular imports. There is no package concept in Umka. What you have done with separating global.um from game.um is a right approach. Moreover, I suppose you may need a small 2D vector math library as a part of tophat. Then your getdist() will become a nice candidate to be included into it.

@marekmaskarinec
Copy link
Contributor Author

marekmaskarinec commented Apr 1, 2021

@vtereshkov I still don't know, how to make it work though. The most problem I have is with types. Enemy needs to have a projectile, but projectile needs to use the enm type when colliding. How should I do that? I tried making a new file for types, but you can't make a function for a foreign type (that is in go too). Only other thing, that I think of is handling the collision in gamescn.um, but what if there will be a case, where this isn't possible?

I suppose you may need a small 2D vector math library as a part of tophat. Then your getdist() will become a nice candidate to be included into it.

When I finnish this game, I will take stuff like getdist(), collisions and stuff like that and implement it into tophat.

@vtereshkov
Copy link
Owner

I still cannot believe that circular dependencies are unavoidable, but I can suggest using interfaces. For example, you need to pass an enemy.enm to fn (p: ^prj) handle() and change its hp field (I don't know if this is your case). Then you can declare in projectile.um:

type hpchanger* = interface {
	sethp(hp: int)
}

fn (p: ^prj) handle*(e: hpchanger): bool {...}

and in enemy.um:

fn (e: ^enm) sethp*(hp: int) {e.hp = hp}

Then in gamescn.um you can call prj[i].handle(&e) where e is an enemy.enm. Pass it by address, otherwise you'll pass its copy.
Please also update the Umka version.

@marekmaskarinec
Copy link
Contributor Author

I don't neeed that. I don't know, which enemy the projectile will collide with. But I think, I can handle this in the game loop, if I return the id from the handle function.

@vtereshkov
Copy link
Owner

It seems to be sensible. But you probably need an ID of any entity, not necessarily enemy, since a projectile may hit a tree or another object.

Anyway, interfaces can be useful for manipulating objects whose actual types are yet undefined. I have to think whether I should recommend this as a proper solution or just as a temporary workaround.

The Go approach is difficult, since it requires multiple compilation passes. Umka has a single-pass compiler.

@marekmaskarinec
Copy link
Contributor Author

I'm able to distinguish, what type of entity it is based on the ID. Player is -1, fire is -10, enemy is > 200 etc. Trees don't generate, when a village is active and even if they did, they would not be added to the scene array. I think interfaces could be a good solution.

@vtereshkov
Copy link
Owner

Using interfaces to break possible import cycles is also encouraged in Go, though it is applied to different packages, not to different files within the same package:

https://medium.com/@ishagirdhar/import-cycles-in-golang-b467f9f0c5a0

@vtereshkov vtereshkov changed the title function not found, even though it exists Alternatives to cyclic imports Apr 3, 2021
@vtereshkov vtereshkov added enhancement New feature or request question Further information is requested and removed bug Something isn't working labels Apr 3, 2021
@marekmaskarinec
Copy link
Contributor Author

Ok. Thanks. I will study a bit on interfaces (never really used them before).

@vtereshkov
Copy link
Owner

The interface concept is Go's alternative to C++/Java classes with virtual functions. You can call interface's methods without even knowing the actual type of the variable converted to the interface. The only requirement is that all the methods declared in the interface are implemented for that type.

Umka follows the same path. See my raytracer.um example to see how interfaces work. I just iterate through the array of bodies and call intersect() for each body. But the bodies are of different sorts: boxes, spheres, etc. Each has its own implementation of intersect(). So I actually call different methods without knowing it.

@vtereshkov
Copy link
Owner

@marekmaskarinec Is this issue still relevant?

@marekmaskarinec
Copy link
Contributor Author

Anyway, interfaces can be useful for manipulating objects whose actual types are yet undefined. I have to think whether I should recommend this as a proper solution or just as a temporary workaround.

This along with the use of tophat's signals and proper thought given to the structure are good ways to mitigate cyclic imports. I think this can be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants