Leaks using GKStateMachine in GameplayKit - memory-leaks

Iā€™m having leak problems with GKStateMachine.
My App is a pretty straight code to test the problem. This is the GameScene:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
lazy var gameState:GKStateMachine = GKStateMachine(states: [Introduction(scene: self)])
override func didMove(to view: SKView) {
self.gameState.enter(Introduction.self)
}
}
And this is my state:
import SpriteKit
import GameplayKit
class Introduction: GKState {
unowned let scene:GameScene
init(scene:SKScene) {
self.scene = scene as! GameScene
super.init()
}
override func didEnter(from previousState: GKState?) {
print("INSIDE THE Introduction STATE")
}
}
The problem is that when I run the Leaks debugger, I received one leak as soon as I enter to the state. Does anybody
has a suggestion?

You can simplify the constructor to avoid the type cast.
init(scene: GameScene) {
self.scene = scene
super.init()
}

Related

Converting Key-Value Observation in AppKit/UIKit to Combine and SwiftUI

I'm having trouble wrapping my head around how to use Combine in SwiftUI. I'm accustomed to using key-value observation in AppKit an UIKit because view controllers don't need to know about each other and can just react to some global objects that help determine state.
For example, in an AppKit/UIKit app, I would create a global state object like this:
//Global State file
#objc class AppState: NSObject {
#objc dynamic var project: Project?
}
//Create an instance I can access anywhere in my app
let app = AppState()
Then in a view controller, I can get notified of any changes to my app-wide project instance and react accordingly:
//View Controller
class MyViewController: NSViewController{
var observerProject: NSKeyValueObservation?
override func viewDidLoad() {
observerProject = app.observe(\.project) { object, change in
self.refreshData()
}
}
func refreshData(){
//Query my persistent store and update my UI
}
}
What is the Combine/SwiftUI analog to this?
Do I need to create a Publisher and then listen to my global object changes? If so, how do I make my Core Data #FetchRequest (whose predicate includes my global Project object) respond in real-time?
I've done things the old way for so long that this transition to SwiftUI/Combine is rather confusing to me. šŸ™‚
#FetchRequest doesn't work well with a dynamic predicate (There are some workarounds in SO) you will have to use the "old school" NSFetchedResultsController for that and put it into an ObservableObject. Here is a video with a sample. It is a lot of setup and code.
import SwiftUI
import Combine
class AppState: ObservableObject {
static let shared = AppState()
#Published var project: String?
//Just to mimic updates
var count = 0
private init() {
//Mimic getting updates to project
Timer.scheduledTimer(withTimeInterval: 2, repeats: true){ timer in
print("Timer")
self.project = self.count.description
self.count += 1
if self.count >= 15{
timer.invalidate()
}
}
}
}
class MyViewModel: ObservableObject{
#Published var refreshedData: String = "init"
let appState: AppState = AppState.shared
var projectCancellable: AnyCancellable?
init() {
projectCancellable = appState.$project.sink(receiveValue: {
value in
print(value ?? "nil")
self.refreshData()
})
}
func refreshData() {
refreshedData = Int.random(in: 0...100).description
}
}
struct MyView: View {
#StateObject var vm: MyViewModel = MyViewModel()
var body: some View {
VStack{
Text(vm.refreshedData)
}
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView()
}
}

How to define custom JS objects in ScalaJS

The phaser game library has an API where you pass a custom object when starting a game scene (docs). This data object can be any javascript object at all and can be retrieved from within the scene from the scene's settings. My question is how do I define this object in the phaser facades in a generic way and define a strongly typed version in my own code?
So far I have just referenced the object as a js.Object in the phaser APIs and cast it to my own type when the scene is created:
#js.native
trait ScenePlugin extends js.Object {
def start(key: SceneKey, data: js.UndefOr[js.Object] = js.undefined): ScenePlugin
}
#js.annotation.ScalaJSDefined
class LevelConfig(
val key: LevelKey,
val loadingImage: Option[AssetKey] = None) extends js.Object
#ScalaJSDefined
class LoadScene extends Scene {
private val loader = new SceneLoader(scene = this)
private var levelConfig: LevelConfig = _
override def preload(): Unit = {
levelConfig = sys.settings.data.asInstanceOf[LevelConfig]
}
...
}
This works but I'm not happy with it because I have to cast the data object. Any errors with the actual object that gets passed to the ScenePlugin.start() will cause errors during runtime and I may as well have just used vanilla JS. Also, my LevelConfig cannot be a case class as I get the compile error Classes and objects extending js.Any may not have a case modifier which I don't fully understand.
Has anyone dealt with this situation before and what did you do to get around it? I'm guessing the issue stems from the library which is being used so perhaps I need to create some kind of wrapper around Phaser's Scene class to deal with this? I'm quite new to ScalaJS and am looking to improve my understanding so any explanations with solutions would be much appreciated (and upvoted). Thanks very much!
I followed Justin du Coeur's comment suggestion of modifying the Phaser facade's. I defined a non-native trait for a SceneData object and updated the native Scene facade to have two types which subclasses of Scene must override. Phaser scenes are abstract and intended to be overridden so I think this works well:
class Scene(config: SceneConfig) extends js.Object {
type Key <: SceneKey
type Data <: SceneData
def scene: ScenePlugin = js.native
def data: Data = js.native
def preload(): Unit = js.native
def create(): Unit = js.native
def update(time: Double, delta: Double): Unit = js.native
}
object Scene {
trait SceneKey { def value: String }
implicit def keyAsString(id: SceneKey): String = id.value
trait SceneData extends js.Object
}
#js.native
trait ScenePlugin extends js.Object {
def start[S <: Scene](id: String, data: js.UndefOr[S#Data] = js.undefined): ScenePlugin = js.native
}
And here's a simplified example of a scene in my game:
class LoadScene extends Scene(LoadScene.Config) {
override type Key = LoadId.type
override type Data = GameAssets
override def preload(): Unit = {
createLoadBar()
loadAssets(data)
}
private def createLoadBar(): Unit = { ... }
private def loadAssets(config: GameAssets): Unit = { ... }
override def create(): Unit = {
scene.start[GameScene](GameId)
}
}
object LoadScene {
case object LoadId extends SceneKey { val value = "loading" }
val Config: SceneConfig = ...
}
I quite like this because it's now impossible to start a scene with another scene's config type.

Core Data Framework With Multiple Targets

I am currently making a project in swift 4 which uses a few targets. Since all the targets need to access the same core data, I have decided to make my own framework target which stores the data model for it as well as the access information for it.
The problem I am having is in my application target (CoreDataTest), when I run the application, I get the following error:
2017-09-17 12:02:20.787132+0100 CoreDataTest[22070:3218298] [error] error: Failed to load model named TestData
CoreData: error: Failed to load model named TestData
2017-09-17 12:02:20.787594+0100 CoreDataTest[22070:3218298] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An NSManagedObject of class 'Message' must have a valid NSEntityDescription.'
*** First throw call stack:
(0x182097d38 0x1815ac528 0x18481a73c 0x1026c6678 0x1026c6708 0x1848bac5c 0x10261d480 0x10261ce94 0x10261cd48 0x10261cecc 0x18b42b96c 0x18b42b544 0x18b43210c 0x18b42f378 0x18b49edb4 0x18b68e570 0x18b693300 0x18b9129b4 0x18bbd90d0 0x18b912618 0x18b912e88 0x18c05daf4 0x18c05d998 0x18bde7ab4 0x18bf77c94 0x18bde7964 0x18bbd8730 0x18b691a44 0x18ba80144 0x18472d968 0x184736270 0x10344145c 0x10344db74 0x184761b04 0x1847617a8 0x184761d44 0x182040358 0x1820402d8 0x18203fb60 0x18203d738 0x181f5e2d8 0x183de3f84 0x18b48f5e0 0x10261deec 0x181a8256c)
libc++abi.dylib: terminating with uncaught exception of type NSException
I added an exception break point and it crashes in ViewController.swift at PersistenceStorage.saveContext().
How would I create a framework to create a shared Core Data database throughout my multiple targets in a single project?
Here is my project setup. Please note that each group is its own target.
Target: CoreDataKit (Framework)
CoreData.swift
import CoreData
public class PersistenceStorage {
private init() {}
public static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
public static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "TestData")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
public static func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Message+CoreDataClass.swift
import Foundation
import CoreData
#objc(Message)
public class Message: NSManagedObject {
}
Message+CoreDataProperties.swift
import Foundation
import CoreData
extension Message {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Message> {
return NSFetchRequest<Message>(entityName: "Message")
}
#NSManaged public var text: String?
}
Target: CoreDataTest (Main Application)
ViewController.swift
import UIKit
import CoreDataKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let message = Message(context: PersistenceStorage.context)
message.text = "test"
PersistenceStorage.saveContext()
}
}
AppDelegate.swift
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
}
After a lot of research and trial and error, I found that I need to set the target members on the xcdatamodel file to the other targets that I wanted it to be shared with.
Just bumped into this myself so thought I'd post a couple other options.
Since you aren't subclassing NSPersistentContainer then it doesn't know where to look for bundles so has no option but to look in the main bundle.
If you want to only have your model in your framework then you could subclass NSPersistentContainer, or you can load the model first and pass that to the initialization of the persistent container. If you have only a single model then doing something like mergedModelFromBundles is pretty simple to do and then pass that model to the persistent container initializer.

Deadlocks with Play 2.5 scala, deadbolt 2.5 and MongoDB/Morphia

Basically all I want to do is getting all users from my database, which worked fine until the very moment i wanted to use deadbolt for it:
I think the 4 Threads(number of processors) of the fork-join-executor are already all used and then there is somekind of deadlock.
Things I tried:
Raise the number of threads the executor has, so however play/akka ignores my settings
Define another execution context for the futures in the controller, but this does not prevent deadlocks since more than four threads still wait at each other
use a thread-pool-executor, but my settings are ignored
A mixed scala/java code from here:
class UserController {
def getUserList = deadbolt.Restrict(List(Array("Admin")))(){ implicit request =>
Future {
val users = userModel.list
val json = Json.toJson(users)
Ok(json.toString)
}(
}
}
The User Model is essentially nothing more than:
public class UserModel {
private MongoClient client = new MongoClient();
private Morphia morphia = new Morphia();
protected Datastore datastore = morphia.createDatastore(client, "timetracking");
public List<User> list(){
return datastore.find(User.class).asList();
}
public User findUserByName(String name){
User found = datastore.createQuery(User.class).field("username").equal(name).get();
return found;
}
}
Authorization Handler:
class AuthorizationHandler extends DeadboltHandler {
val model = new UserModel
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] =
Future {
blocking {
request.subject match {
case Some(user) =>
request.subject
case None =>
val username = request.session.get("username")
if (username.isDefined) {
val user = model.findUserByName(username.get)
if (user == null) {
None
} else {
val subject = new ScalaSubject(user.getUsername, user.getRole)
Some(subject)
}
} else {
None
}
}
}
}
Defining a seperate deadbolt context does not help:
package deadbolt.scala
import be.objectify.deadbolt.scala.DeadboltExecutionContextProvider
import be.objectify.deadbolt.scala.cache.HandlerCache
import play.api.inject.{Binding, Module}
import play.api.{Configuration, Environment}
class DeadBoldModule extends Module {
override def bindings(environment: Environment,
configuration: Configuration): Seq[Binding[_]] = Seq(
bind[HandlerCache].to[TimeTrackerHandelCache],
bind[DeadboltExecutionContextProvider].to[ThreadPoolProvider]
)
}
Custom context provider:
package deadbolt.scala
import java.io.InvalidObjectException
import java.util.concurrent.Executors
import be.objectify.deadbolt.scala.DeadboltExecutionContextProvider
import scala.concurrent.ExecutionContext
class ThreadPoolProvider extends DeadboltExecutionContextProvider {
override def get(): ExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(100))
}
When I try this, throwing some random exception, it is never thrown:
package deadbolt.scala
import java.io.InvalidObjectException
import java.util.concurrent.Executors
import be.objectify.deadbolt.scala.DeadboltExecutionContextProvider
import scala.concurrent.ExecutionContext
class ThreadPoolProvider extends DeadboltExecutionContextProvider {
override def get(): ExecutionContext = throw new IllegalAccessError("asd");ExecutionContext.fromExecutor(Executors.newFixedThreadPool(100))
}
It was not deadbolts fault, but however the MongoClient opened a new thread when it was instantiated.Which happened in our project quite often, but was not closed properly thereby blocking the threadpool. We used a Singleton and everything worked fine.

OpenFL - How to use addChild in another class?

The answer should be simple but I can't find out how anywhere..
So I have a Main.hx class and a ObjectManager.hx class.
In the main class's constructor, it calls ObjectManager.addAllToScreen(); and my objectManager should then add the objects to the screen.
I thought this would be ok because in the constructor of Main you can just say addChild(Objects.platform); but apparently addChild isn't accessible?
The error is: Class<Main> has no field addChild so I'd guess that addChild is a method of Sprite or something?
package;
class Main extends Sprite {
public function new() {
super();
ObjectManager.addAllToScreen();
}
}
In ObjectManager:
package;
class ObjectManager {
public static function addAllToScreen():Void {
Main.addChild(Objects.platform);
Main.addChild(Objects.messageField);
}
}
UPDATE:
Ok so now the code is this... and it runs just fine apart from the objects never showing up on screen - however if the addChild code is put in main, they do show up.
Main:
class Main extends Sprite {
public function new() {
super();
var objectManager = new ObjectManager();
objectManager.addAllToScreen();
}
ObjectManager:
package;
import openfl.display.Sprite;
class ObjectManager extends Sprite {
public function new() {
super();
}
public function addAllToScreen() {
addChild(Objects.platform);
addChild(Objects.messageField);
}
}
addChild() is available in openfl.DisplayObjectContainer, which Sprite extends. So you would need to make your class extend Sprite, yes.
You just need to pass a reference to the stage to your ObjectManager class so you can add things to it later on.
Check this out.
Main.hx
package;
import openfl.display.Sprite;
class Main extends Sprite {
public function new () {
super();
ObjectManager.setup(stage);
ObjectManager.addAllToScreen();
}
}
ObjectManager.hx
package ;
import openfl.display.Sprite;
import openfl.display.Stage;
class ObjectManager {
// The reference to the applications stage
private static var stage:Stage;
// Do this first,
// we need to hold a reference to the Stage object so we can add to it later
public static function setup(stageref:Stage) {
stage = stageref;
}
public static function addAllToScreen() {
// An array of randomly generated sprite objects
var sprites:Array<Sprite> = [randomSprite(), randomSprite(), randomSprite()];
for(sprite in sprites) {
// Position the sprites randomly over the screen
sprite.x = Math.random() * stage.stageWidth;
sprite.y = Math.random() * stage.stageHeight;
// Add them to the stage
stage.addChild(sprite);
}
}
// Ignore this
// Makes a randomly sized circle in a random colour
private static function randomSprite():Sprite {
var spr = new Sprite();
spr.graphics.beginFill(Std.int(0xffffff * Math.random()), 1);
spr.graphics.drawCircle(0, 0, (Math.random() * 100) + 20);
spr.graphics.endFill();
return spr;
}
}

Resources