Able to launch game now

This commit is contained in:
huangyuhui
2017-08-08 23:57:17 +08:00
parent f1ffa5ca03
commit da66102bc0
62 changed files with 1790 additions and 744 deletions

View File

@@ -24,5 +24,5 @@ abstract class Account() {
@Throws(AuthenticationException::class)
abstract fun logIn(proxy: Proxy = Proxy.NO_PROXY): AuthInfo
abstract fun logOut()
abstract fun toStorage(): Map<out Any, Any>
abstract fun toStorage(): MutableMap<Any, Any>
}

View File

@@ -0,0 +1,27 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
object Accounts {
val ACCOUNTS = mapOf(
"offline" to OfflineAccount,
"yggdrasil" to YggdrasilAccount
)
}

View File

@@ -35,13 +35,15 @@ class OfflineAccount private constructor(val uuid: String, override val username
// Offline account need not log out.
}
override fun toStorage(): Map<Any, Any> {
return mapOf(
override fun toStorage(): MutableMap<Any, Any> {
return mutableMapOf(
"uuid" to uuid,
"username" to username
)
}
override fun toString() = "OfflineAccount[username=$username,uuid=$uuid]"
companion object OfflineAccountFactory : AccountFactory<OfflineAccount> {
override fun fromUsername(username: String, password: String): OfflineAccount {

View File

@@ -135,8 +135,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
selectedProfile = null
}
override fun toStorage(): Map<out Any, Any> {
val result = HashMap<String, Any>()
override fun toStorage(): MutableMap<Any, Any> {
val result = HashMap<Any, Any>()
result[STORAGE_KEY_USER_NAME] = username
if (userId != null)
@@ -189,6 +189,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
}
}
override fun toString() = "YggdrasilAccount[username=$username]"
companion object YggdrasilAccountFactory : AccountFactory<YggdrasilAccount> {
private val GSON = GsonBuilder()
.registerTypeAdapter(GameProfile::class.java, GameProfile)

View File

@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.launch
import org.jackhuang.hmcl.auth.AuthInfo
import org.jackhuang.hmcl.game.*
import org.jackhuang.hmcl.task.TaskResult
import org.jackhuang.hmcl.util.*
import java.io.File
import java.io.IOException
@@ -26,12 +27,12 @@ import java.util.*
import kotlin.concurrent.thread
/**
* @param version A resolved version(calling [Version.resolve])
* @param versionId The version to be launched.
* @param account The user account
* @param options The launching configuration
*/
open class DefaultLauncher(repository: GameRepository, version: Version, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
: Launcher(repository, version, account, options, listener, isDaemon) {
open class DefaultLauncher(repository: GameRepository, versionId: String, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
: Launcher(repository, versionId, account, options, listener, isDaemon) {
protected val native: File by lazy { repository.getNativeDirectory(version.id) }
@@ -234,7 +235,7 @@ open class DefaultLauncher(repository: GameRepository, version: Version, account
}
builder.directory(repository.getRunDirectory(version.id))
.environment().put("APPDATA", options.gameDir.parent)
.environment().put("APPDATA", options.gameDir.absoluteFile.parent)
val p = JavaProcess(builder.start(), rawCommandLine)
if (listener == null)
startMonitors(p)
@@ -243,6 +244,14 @@ open class DefaultLauncher(repository: GameRepository, version: Version, account
return p
}
fun launchAsync(): TaskResult<JavaProcess> {
return object : TaskResult<JavaProcess>() {
override fun execute() {
result = launch()
}
}
}
override fun makeLaunchScript(file: String): File {
val isWindows = OS.WINDOWS == OS.CURRENT_OS
val scriptFile = File(file + (if (isWindows) ".bat" else ".sh"))

View File

@@ -26,12 +26,13 @@ import java.io.File
abstract class Launcher(
protected val repository: GameRepository,
protected val version: Version,
protected val versionId: String,
protected val account: AuthInfo,
protected val options: LaunchOptions,
protected val listener: ProcessListener? = null,
protected val isDaemon: Boolean = true) {
val version: Version = repository.getVersion(versionId).resolve(repository)
abstract val rawCommandLine: List<String>
abstract fun launch(): JavaProcess

View File

@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.task
private class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() {
internal class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() {
override val hidden: Boolean = true
override val dependents: Collection<Task> = listOf(pred)
@@ -30,11 +30,12 @@ private class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P
}
}
infix fun Task.then(b: Task): Task = CoupleTask(this, { b }, true)
/**
* @param b A runnable that decides what to do next, You can also do something here.
*/
infix fun <T: Task> T.then(b: Task.(T) -> Task?): Task = CoupleTask(this, b, true)
infix fun Task.with(b: Task): Task = CoupleTask(this, { b }, false)
/**
* @param b A runnable that decides what to do next, You can also do something here.
*/
infix fun <T: Task> T.with(b: Task.(T) -> Task?): Task = CoupleTask(this, b, false)

View File

@@ -23,16 +23,17 @@ import java.util.concurrent.atomic.AtomicReference
import javax.swing.SwingUtilities
interface Scheduler {
fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() })
fun schedule(block: Callable<Unit>): Future<*>?
companion object Schedulers {
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
private class SchedulerImpl(val executor: (() -> Unit) -> Unit) : Scheduler {
private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler {
override fun schedule(block: Callable<Unit>): Future<*>? {
val latch = CountDownLatch(1)
val wrapper = AtomicReference<Exception>()
executor {
executor.invoke(Runnable {
try {
block.call()
} catch (e: Exception) {
@@ -40,7 +41,7 @@ interface Scheduler {
} finally {
latch.countDown()
}
}
})
return object : Future<Unit> {
override fun get(timeout: Long, unit: TimeUnit) {
latch.await(timeout, unit)
@@ -66,7 +67,9 @@ interface Scheduler {
}
val DEFAULT = NEW_THREAD
private val CACHED_EXECUTOR: ExecutorService by lazy {
Executors.newCachedThreadPool()
ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
SynchronousQueue<Runnable>());
}
private val IO_EXECUTOR: ExecutorService by lazy {

View File

@@ -57,6 +57,8 @@ abstract class Task {
abstract fun execute()
infix fun parallel(couple: Task): Task = ParallelTask(this, couple)
infix fun then(b: Task): Task = CoupleTask(this, { b }, true)
infix fun with(b: Task): Task = CoupleTask(this, { b }, false)
/**
* The collection of sub-tasks that should execute before this task running.
@@ -112,18 +114,18 @@ abstract class Task {
fun executor() = TaskExecutor().submit(this)
fun subscribe(subscriber: Task) {
executor().submit(subscriber).start()
fun subscribe(subscriber: Task) = executor().apply {
submit(subscriber).start()
}
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(Task.of(closure, scheduler))
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(Task.of(scheduler, closure))
override fun toString(): String {
return title
}
companion object {
fun of(closure: () -> Unit, scheduler: Scheduler = Scheduler.DEFAULT): Task = SimpleTask(closure, scheduler)
fun of(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit): Task = SimpleTask(closure, scheduler)
fun <V> of(callable: Callable<V>): TaskResult<V> = TaskCallable(callable)
}
}

View File

@@ -29,7 +29,7 @@ class TaskExecutor() {
var canceled = false
private set
private val totTask = AtomicInteger(0)
val totTask = AtomicInteger(0)
private val taskQueue = ConcurrentLinkedQueue<Task>()
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
@@ -47,9 +47,10 @@ class TaskExecutor() {
* Start the subscription and run all registered tasks asynchronously.
*/
fun start() {
thread {
workerQueue.add(Scheduler.Schedulers.NEW_THREAD.schedule(Callable {
totTask.addAndGet(taskQueue.size)
while (!taskQueue.isEmpty() && !canceled) {
while (!taskQueue.isEmpty()) {
if (canceled) break
val task = taskQueue.poll()
if (task != null) {
val future = task.scheduler.schedule(Callable { executeTask(task); Unit })
@@ -61,9 +62,9 @@ class TaskExecutor() {
}
}
}
if (!canceled)
if (canceled || Thread.interrupted())
taskListener?.onTerminate()
}
}))
}
/**
@@ -91,9 +92,11 @@ class TaskExecutor() {
if (future != null)
workerQueue.add(future)
}
if (canceled)
return false
try {
counter.await()
return success.get()
return success.get() && !canceled
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
// Once interrupted, we are aborting the subscription.
@@ -110,9 +113,10 @@ class TaskExecutor() {
LOG.fine("Executing task: ${t.title}")
taskListener?.onReady(t)
val doDependentsSucceeded = executeTasks(t.dependents)
var flag = false
try {
if (!doDependentsSucceeded && t.reliant)
if (!doDependentsSucceeded && t.reliant || canceled)
throw SilentException()
t.execute()

View File

@@ -52,6 +52,7 @@ data class JavaVersion internal constructor(
}
}
@Throws(IOException::class)
fun fromExecutable(file: File): JavaVersion {
var platform = Platform.BIT_32
var version: String? = null
@@ -66,6 +67,7 @@ data class JavaVersion internal constructor(
platform = Platform.BIT_64
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
throw IOException("Java process is interrupted", e)
}
val thisVersion = version ?: throw IOException("Java version not matched")

View File

@@ -49,9 +49,9 @@ enum class OS {
ReflectionHelper.get<Long>(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize") ?: 1024L
}
val SUGGESTED_MEMORY: Long by lazy {
val SUGGESTED_MEMORY: Int by lazy {
val memory = TOTAL_MEMORY / 1024 / 1024 / 4
Math.round(1.0 * memory / 128.0) * 128
(Math.round(1.0 * memory / 128.0) * 128).toInt()
}
val PATH_SEPARATOR: String = File.pathSeparator

View File

@@ -19,7 +19,7 @@ package org.jackhuang.hmcl
import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
import org.jackhuang.hmcl.download.LiteLoaderVersionList
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.game.DefaultGameRepository
@@ -50,7 +50,7 @@ class Test {
fun launch() {
val launcher = DefaultLauncher(
repository = repository,
version = repository.getVersion("test"),
versionId = "test",
account = OfflineAccount.fromUsername("player007").logIn(),
options = LaunchOptions(gameDir = repository.baseDirectory),
listener = object : ProcessListener {