https://kotlinlang.org logo
#getting-started
Title
# getting-started
a

Amir Eldor

09/23/2020, 8:14 PM
Hello. I'm trying to do something like this and not sure what I'm doing. I want to have a Factory of, let's say, Ship. A Game has a mutable list (or whatever) of Ships so I can track all the Ships in my game (more in thread) -->
The Factory gets a reference to Game when I first create it and when building a Ship the Factory registers the Ship in its Game reference. However, the developer can still create classes of Ship outside the factory. I know I can give Ship a private constructor, but how would I allow the factory to create Ships that way? If I use a companion object I will need to provide it with the Game reference each time and it kind of loses the purpose of creating the Factory with the Game reference once. In C++land I'd probably use friend or something like that. How do you do that in Kotlin? Thanks.
Or maybe there's a smarter way to solve the "let's track all Ships" problem
n

nanodeath

09/23/2020, 8:34 PM
However, the developer can still create classes of Ship outside the factory.
is this a feature or a bug
a

Amir Eldor

09/23/2020, 8:42 PM
It's a bug as this way Game will not track the new Ship
That's why I want to centralize the creation of Ship in a Factory with a reference to a game (there may be different games at the same time :()
Maybe there's a trick with the
internal
keyword, but maybe I'm barking at the wrong tree
n

nanodeath

09/23/2020, 8:49 PM
depends on whether you want people to be able to subclass Ship. if no, then saying
class Ship internal constructor(...)
should be sufficient, then making
ShipFactory.newShip(...)
public
if yes (subclassing permitted), you maybe could make
Ship
itself accept
Game
as a constructor argument that Ship registers with in the constructor, but...passing
this
to other objects from a constructor is problematic from various perspectives (security, exception-handling)
or finally, just say "hey you have to call
Game.addEntity(ship)
or it ain't gonna work"
a

Amir Eldor

09/23/2020, 8:53 PM
Ok, cool, so two question:
I made the internal Ship class inside package
a.stuff.game
, but when I created a Ship instance in
a.stuff.somethingelse
the compiler did not complain. Is this expected as the
a.stuff
is common?
n

nanodeath

09/23/2020, 8:55 PM
internal
works pretty differently from
package private
--
internal
is accessible anywhere in the entire "compilation unit", e.g. your library/engine
if your game and engine are in the same compilation unit and you're trying to keep yourself from calling the Ship constructor directly...that's going to require more thought
internal is documented here
a

Amir Eldor

09/23/2020, 8:57 PM
Yeah I've read this, by 'module' they mean "compilation unit"?
internal
— any client inside this module who sees the declaring class sees its
internal
members;
n

nanodeath

09/23/2020, 8:57 PM
yeah, module as in like...a Gradle project/subproject
a

Amir Eldor

09/23/2020, 8:57 PM
I mistook that for package, alright. Thanks
n

nanodeath

09/23/2020, 8:57 PM
I keep thinking "package" but that's a ridiculously overloaded term
I don't care much for Scala but it's ridiculously fine-grained access controls were nice sometimes 🙂
a

Amir Eldor

09/23/2020, 8:59 PM
I didn't have the pleasure to play with Scala, not sure if I will 😅
Regarding Ship registering itself in game in a constructor, there's no real safe way to do that because the Ship's
this
is incomplete
I read about some lateinit magic, but not sure if that's a thing
And well, bottom line, I think the best way is just to register the Ship manually after creating it or inside a Factory - OR go the ECS route and be a happier person
n

nanodeath

09/23/2020, 9:01 PM
lateinit is for certain var fields set outside the constructor
incidentally, the ECS route also probably requires you explicitly registering some things with the World. I'm writing an ECS for KorGE right now and running into that -- systems and components have to be manually registered with World which sucks
a

Amir Eldor

09/23/2020, 9:04 PM
Hello KorGE, nice to meet you.
Although I'm creating something a bit different
v

Vampire

09/23/2020, 10:49 PM
Make the constructor private and make the factory method in the companion object
n

nanodeath

09/23/2020, 11:40 PM
I've found
private
to be way stricter than Java's
private
when it comes to inner classes and the like
v

Vampire

09/24/2020, 9:32 AM
Even if so, Companion object can pretty well access the private constructor.
a

Amir Eldor

09/24/2020, 4:10 PM
@Vampire can I create a companion object with a reference to a
Game
? so every call to companion's buildShip could reference that Game I initially gave?
v

Vampire

09/24/2020, 5:17 PM
You can set a property of the companion object
a

Amir Eldor

09/24/2020, 7:16 PM
Like that!
v

Vampire

09/24/2020, 7:36 PM
Yeah, that works too. I had something in mind like (untested written on mobile)
Copy code
kt
class Ship private constructor() : Tickable {
    companion object {
        lateinit val game: Game
        fun create() = Ship().also(game::register)
    }

    override fun tick() {
        println("tock")
    }
}

...
Ship.game = Game()
Ship.create()
a

Amir Eldor

09/24/2020, 7:41 PM
Thanks, looks cool! But this way Ship is tied to a single Game instance, right? I might have another game running in parallel which needs ships created in it and not the former.
v

Vampire

09/24/2020, 7:54 PM
Yeah, then the property approach doesn't work of course, so something like you have or like this:
Copy code
Yeah, that works too. I had something in mind like (untested written on mobile)
```kt
class Ship private constructor() : Tickable {
    companion object {
        fun producer(game: Game) : () -> Ship = {
            Ship().also(game::register)
        }
    }

    override fun tick() {
        println("tock")
    }
}

...
Ship.game = Game()
Ship.create()
a

Amir Eldor

09/25/2020, 12:10 AM
thanks, I'll look into that fancy .also syntax too