Skip to content

Commit

Permalink
refactor: Partial move stateful services to stateless
Browse files Browse the repository at this point in the history
  • Loading branch information
ci010 committed Oct 4, 2023
1 parent 8b96e4f commit de4fb64
Show file tree
Hide file tree
Showing 27 changed files with 369 additions and 494 deletions.
21 changes: 8 additions & 13 deletions xmcl-electron-app/preload/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@ import { ServiceChannels, ServiceKey } from '@xmcl/runtime-api'
import { contextBridge, ipcRenderer } from 'electron'
import EventEmitter from 'events'

async function waitSessionEnd(sessionId: number, listener: (task: string) => void) {
ipcRenderer.on(`session-${sessionId}`, (e, task) => listener(task))
try {
const { result, error } = await ipcRenderer.invoke('session', sessionId)
if (error) {
if (error.errorMessage) {
error.toString = () => error.errorMessage
}
return Promise.reject(error)
async function waitSessionEnd(sessionId: number) {
const { result, error } = await ipcRenderer.invoke('session', sessionId)
if (error) {
if (error.errorMessage) {
error.toString = () => error.errorMessage
}
return result
} finally {
ipcRenderer.removeAllListeners(`session-${sessionId}`)
return Promise.reject(error)
}
return result
}

function createServiceChannels(): ServiceChannels {
Expand Down Expand Up @@ -68,7 +63,7 @@ function createServiceChannels(): ServiceChannels {
if (typeof sessionId !== 'number') {
throw new Error(`Cannot find service call named ${method as string} in ${serviceKey}`)
}
return waitSessionEnd(sessionId, (id) => emitter.emit('task', { name: method, promise, sessionId, id }))
return waitSessionEnd(sessionId)
})
return promise
},
Expand Down
34 changes: 0 additions & 34 deletions xmcl-keystone-ui/src/composables/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,40 +63,6 @@ export function useInstanceVersionBase() {
}
}

export function useInstanceGameSetting() {
const { state, refresh: _refresh, editGameSetting: edit, showOptionsFileInFolder: showInFolder } = useService(InstanceOptionsServiceKey)
const refresh = () => _refresh()
const fancyGraphics = computed(() => state.options.fancyGraphics)
const renderClouds = computed(() => state.options.renderClouds)
const ao = computed(() => state.options.ao)
const entityShadows = computed(() => state.options.entityShadows)
// const particles = computed(() => state.options.particles)
// const mipmapLevels = computed(() => state.options.mipmapLevels)
const useVbo = computed(() => state.options.useVbo)
const fboEnable = computed(() => state.options.fboEnable)
const enableVsync = computed(() => state.options.enableVsync)
const anaglyph3d = computed(() => state.options.anaglyph3d)

return {
fancyGraphics,
renderClouds,
ao,
entityShadows,
// particles,
// mipmapLevels,
useVbo,
fboEnable,
enableVsync,
anaglyph3d,
showInFolder,
refreshing: useServiceBusy(InstanceOptionsServiceKey, 'mount'),
refresh,
commit(settings: GameSetting) {
edit(settings)
},
}
}

/**
* Use references of all the version info of this instance
*/
Expand Down
5 changes: 4 additions & 1 deletion xmcl-keystone-ui/src/composables/instanceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useModsSearch } from './modSearch'
import { useModSearchItems } from './modSearchItems'
import { useInstanceMods } from './mod'
import { useService } from './service'
import { InstanceJavaServiceKey } from '@xmcl/runtime-api'
import { useInstanceOptions } from './instanceOptions'

/**
* The context to hold the instance related data. This is used to share data between different components.
Expand All @@ -22,6 +22,8 @@ export function useInstanceContext() {
const java = computed(() => javaState.java)
const isServer = useInstanceIsServer(instance)

const options = useInstanceOptions(instance)

const modSearch = useModsSearch(ref(''), version)
const modSearchItems = useModSearchItems(modSearch.keyword, modSearch.modrinth, modSearch.curseforge, modSearch.mods, modSearch.existedMods)
const mods = useInstanceMods(version, java)
Expand All @@ -33,6 +35,7 @@ export function useInstanceContext() {
name,
mods,
version,
options,
localVersion,
minecraft,
forge,
Expand Down
9 changes: 9 additions & 0 deletions xmcl-keystone-ui/src/composables/instanceOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Instance, InstanceOptionsServiceKey } from '@xmcl/runtime-api'
import { Ref } from 'vue'
import { useService } from './service'
import { useState } from './syncableState'

export function useInstanceOptions(instance: Ref<Instance>) {
const { watchOptions } = useService(InstanceOptionsServiceKey)
return useState(computed(() => instance.value.path), () => watchOptions(instance.value.path))
}
9 changes: 6 additions & 3 deletions xmcl-keystone-ui/src/composables/resourcePack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import unknownPack from '@/assets/unknown_pack.png'
import { useService, useServiceBusy } from '@/composables'
import { isStringArrayEquals } from '@/util/equal'
import { kResourcePacks, useResourcePacks } from './resourcePacks'
import { injection } from '@/util/inject'
import { kInstanceContext } from './instanceContext'

export interface ResourcePackItem extends PackMeta.Pack {
/**
Expand Down Expand Up @@ -43,7 +45,8 @@ export interface ResourcePackItem extends PackMeta.Pack {
* The hook return a reactive resource pack array.
*/
export function useInstanceResourcePacks() {
const { state: gameSettingState, editGameSetting } = useService(InstanceOptionsServiceKey)
const { options: { state: options }, path } = injection(kInstanceContext)
const { editGameSetting } = useService(InstanceOptionsServiceKey)
const { resources, refreshing } = inject(kResourcePacks, () => useResourcePacks(), true)
const { updateResources } = useService(ResourceServiceKey)
const { showDirectory } = useService(InstanceResourcePacksServiceKey)
Expand All @@ -57,7 +60,7 @@ export function useInstanceResourcePacks() {
* It should be something like ['file/pack.zip', 'vanilla']
*/
const enabledResourcePackNames: Ref<string[]> = ref([])
const optionsResourcePacks = computed(() => gameSettingState.options.resourcePacks)
const optionsResourcePacks = computed(() => options.value?.options.resourcePacks || [])
/**
* Enabled pack item
*/
Expand Down Expand Up @@ -179,7 +182,7 @@ export function useInstanceResourcePacks() {
* Commit the change for current mods setting
*/
function commit() {
editGameSetting({ resourcePacks: [...enabledResourcePackNames.value].reverse() })
editGameSetting({ instancePath: path.value, resourcePacks: [...enabledResourcePackNames.value].reverse() })
const modified = storage.value.filter(v => v.resource).filter((v) => v.name !== v.resource!.name || !isStringArrayEquals(v.tags, v.resource!.tags))
updateResources(modified.map(res => ({ ...res.resource!, name: res.name, tags: res.tags })))
}
Expand Down
5 changes: 4 additions & 1 deletion xmcl-keystone-ui/src/composables/save.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { CloneSaveOptions, DeleteSaveOptions, ImportSaveOptions, InstanceSavesServiceKey } from '@xmcl/runtime-api'
import { useInstanceBase } from './instance'
import { useService } from '@/composables'
import { injection } from '@/util/inject'
import { kInstanceContext } from './instanceContext'

export function useInstanceSaves() {
const { } = injection(kInstanceContext)
const { path } = useInstanceBase()
const { state, cloneSave, deleteSave, exportSave, readAllInstancesSaves, importSave, mountInstanceSaves } = useService(InstanceSavesServiceKey)
const { cloneSave, deleteSave, exportSave, importSave } = useService(InstanceSavesServiceKey)
const refresh = () => mountInstanceSaves(path.value)
return {
refresh,
Expand Down
17 changes: 10 additions & 7 deletions xmcl-keystone-ui/src/composables/shaderpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'

import { useRefreshable, useService } from '@/composables'
import { kShaderPacks, useShaderPacks } from './shaderPacks'
import { kInstanceContext } from './instanceContext'
import { injection } from '@/util/inject'

export interface ShaderPackItem {
name: string
Expand All @@ -16,31 +18,32 @@ export interface ShaderPackItem {
}

export function useShaderpacks() {
const { options: { state: options }, path } = injection(kInstanceContext)
const { resources: shaderPacksResources, refreshing: loading } = inject(kShaderPacks, () => useShaderPacks(), true)
const { updateResources, removeResources } = useService(ResourceServiceKey)
const { state: options, editShaderOptions } = useService(InstanceOptionsServiceKey)
const { editShaderOptions } = useService(InstanceOptionsServiceKey)
const { showDirectory } = useService(InstanceShaderPacksServiceKey)
const { t } = useI18n()

const shaderPacks = ref([] as ShaderPackItem[])
const selectedShaderPack = ref(options.shaderoptions.shaderPack)
const selectedShaderPack = ref(options.value?.shaderoptions.shaderPack)

const isModified = computed(() => selectedShaderPack.value !== options.shaderoptions.shaderPack)
const isModified = computed(() => selectedShaderPack.value !== options.value?.shaderoptions.shaderPack)

function getBuiltinItems(): ShaderPackItem[] {
return [{
name: t('shaderPack.off'),
value: 'OFF',
resource: null as any,
enabled: options.shaderoptions.shaderPack === 'OFF',
enabled: options.value?.shaderoptions.shaderPack === 'OFF',
description: t('shaderPack.offDescription'),
path: '',
tags: [],
}, {
name: t('shaderPack.internal'),
value: '(internal)',
resource: null as any,
enabled: options.shaderoptions.shaderPack === '(internal)',
enabled: options.value?.shaderoptions.shaderPack === '(internal)',
description: t('shaderPack.internalDescription'),
path: '',
tags: [],
Expand All @@ -52,15 +55,15 @@ export function useShaderpacks() {
name: res.name,
value: fileName,
resource: res,
enabled: fileName === options.shaderoptions.shaderPack,
enabled: fileName === options.value?.shaderoptions.shaderPack,
description: res.path,
path: res.path,
tags: [...res.tags],
icon: res.icons?.[0],
}
}
const { refresh: commit, refreshing: committing } = useRefreshable(async () => {
editShaderOptions({ shaderPack: selectedShaderPack.value })
editShaderOptions({ instancePath: path.value, shaderPack: selectedShaderPack.value || '' })
})

function updateResourceTags() {
Expand Down
29 changes: 29 additions & 0 deletions xmcl-keystone-ui/src/composables/syncableState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Disposable } from '@xmcl/runtime-api'
import { Ref } from 'vue'

export function useState<T>(key: Ref<string>, fetcher: () => Promise<Disposable<T>>) {
const isValidating = ref(false)
const state = ref<T | undefined>()
let dispose = () => { }
const error = ref(undefined as any)
const mutate = () => {
isValidating.value = true
dispose()
fetcher().then((v) => {
state.value = v
dispose = v.dispose
}, (e) => {
error.value = e
}).finally(() => {
isValidating.value = false
})
}
watch(key, mutate)
onMounted(mutate)
onUnmounted(dispose)
return {
isValidating,
state,
error,
}
}
2 changes: 1 addition & 1 deletion xmcl-keystone-ui/src/views/HomeScreenshotCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const { on } = useService(LaunchServiceKey)
const urls = ref([] as string[])
const { refresh, refreshing } = useRefreshable(async () => {
const result = await getScreenshots()
const result = await getScreenshots(props.instance.path)
if (result.length === 0) {
urls.value = ['']
} else {
Expand Down
20 changes: 6 additions & 14 deletions xmcl-keystone-ui/src/windows/main/services.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { kServiceFactory } from '@/composables'
import { injection } from '@/util/inject'
import type { ResolvedVersion } from '@xmcl/core'
import { BaseServiceKey, BaseState, CurseForgeServiceKey, DiagnoseServiceKey, DiagnoseState, EMPTY_JAVA, EMPTY_VERSION, GameProfileAndTexture, ImportServiceKey, InstallServiceKey, InstanceInstallServiceKey, InstanceIOServiceKey, InstanceJavaServiceKey, InstanceJavaState, InstanceLogServiceKey, InstanceManifestServiceKey, InstanceModsServiceKey, InstanceModsState, InstanceOptionsServiceKey, InstanceOptionsState, InstanceResourcePacksServiceKey, InstanceSavesServiceKey, InstanceSchema, InstanceScreenshotServiceKey, InstanceServerInfoServiceKey, InstanceServiceKey, InstanceShaderPacksServiceKey, InstanceState, InstanceUpdateServiceKey, InstanceVersionServiceKey, InstanceVersionState, JavaRecord, JavaServiceKey, JavaState, LaunchServiceKey, LaunchState, LittleSkinUserServiceKey, LocalVersionHeader, ModpackServiceKey, ModrinthServiceKey, NatDeviceInfo, NatServiceKey, NatState, OfficialUserServiceKey, OfflineUserServiceKey, PeerServiceKey, PeerState, Resource, ResourcePackPreviewServiceKey, ResourceServiceKey, SaveState, ServerInfoState, ServerStatusServiceKey, UserProfile, UserServiceKey, UserState, VersionServiceKey, VersionState } from '@xmcl/runtime-api'
import { BaseServiceKey, BaseState, CurseForgeServiceKey, DiagnoseServiceKey, DiagnoseState, EMPTY_VERSION, GameProfileAndTexture, ImportServiceKey, InstallServiceKey, InstanceInstallServiceKey, InstanceIOServiceKey, InstanceLogServiceKey, InstanceManifestServiceKey, InstanceModsServiceKey, InstanceModsState, InstanceOptionsServiceKey, InstanceResourcePacksServiceKey, InstanceSavesServiceKey, InstanceSchema, InstanceScreenshotServiceKey, InstanceServerInfoServiceKey, InstanceServiceKey, InstanceShaderPacksServiceKey, InstanceState, InstanceUpdateServiceKey, InstanceVersionServiceKey, InstanceVersionState, JavaServiceKey, JavaState, LaunchServiceKey, LaunchState, LittleSkinUserServiceKey, LocalVersionHeader, ModpackServiceKey, ModrinthServiceKey, NatDeviceInfo, NatServiceKey, NatState, OfficialUserServiceKey, OfflineUserServiceKey, PeerServiceKey, PeerState, Resource, ResourcePackPreviewServiceKey, ResourceServiceKey, ServerStatusServiceKey, UserProfile, UserServiceKey, UserState, VersionServiceKey, VersionState } from '@xmcl/runtime-api'
import { DeepPartial } from '@xmcl/runtime-api/src/util/object'
import { GameProfile } from '@xmcl/user'
import { del, set } from 'vue'

class ReactiveInstanceJavaState extends InstanceJavaState {
java = EMPTY_JAVA

instanceJava(java: JavaRecord | undefined) {
set(this, 'java', java)
}
}

class ReactiveInstanceVersionState extends InstanceVersionState {
versionHeader = EMPTY_VERSION
instanceVersion(version: ResolvedVersion | undefined) {
Expand Down Expand Up @@ -139,18 +131,18 @@ export function useAllServices() {
factory.register(ModrinthServiceKey, () => undefined)
factory.register(CurseForgeServiceKey, () => undefined)
factory.register(InstanceScreenshotServiceKey, () => undefined)
factory.register(InstanceOptionsServiceKey, () => undefined)
factory.register(InstanceModsServiceKey, () => undefined)
factory.register(InstanceSavesServiceKey, () => undefined)
factory.register(InstanceServerInfoServiceKey, () => undefined)

factory.register(NatServiceKey, () => new ReactiveNatState())
factory.register(InstanceJavaServiceKey, () => new ReactiveInstanceJavaState())
factory.register(InstanceVersionServiceKey, () => new ReactiveInstanceVersionState())
factory.register(PeerServiceKey, () => new PeerState())
factory.register(BaseServiceKey, () => new BaseState())
factory.register(DiagnoseServiceKey, () => new DiagnoseState())
factory.register(InstanceOptionsServiceKey, () => new InstanceOptionsState())
factory.register(InstanceModsServiceKey, () => new ReactiveInstanceModState())
factory.register(InstanceSavesServiceKey, () => new SaveState())
factory.register(InstanceServerInfoServiceKey, () => new ServerInfoState())
factory.register(InstanceServiceKey, () => new ReactiveInstanceState())
factory.register(InstanceServiceKey, () => new InstanceState())
factory.register(JavaServiceKey, () => new JavaState())
factory.register(VersionServiceKey, () => new VersionState())
factory.register(LaunchServiceKey, () => new LaunchState())
Expand Down
2 changes: 1 addition & 1 deletion xmcl-runtime-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export * from './src/services/ImportService'
export * from './src/services/InstallService'
export * from './src/services/ModpackService'
export * from './src/services/LittleSkinUserService'
export * from './src/services/InstanceJavaService'
export * from './src/services/InstanceLogService'
export * from './src/services/InstanceScreenshotService'
export * from './src/services/InstanceIOService'
Expand Down Expand Up @@ -80,3 +79,4 @@ export { default as packFormatVersionRange } from './src/util/packFormatVersionR
export { default as protocolToMinecraft } from './src/util/protocolToMinecraft'
export * from './src/util/versionRange'
export * from './src/util/mavenVersion'
export * from './src/util/Disposable'
16 changes: 8 additions & 8 deletions xmcl-runtime-api/src/services/InstanceLogService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,41 @@ export interface InstanceLogService {
/**
* List the log in current instances
*/
listLogs(): Promise<string[]>
listLogs(instancePath: string): Promise<string[]>
/**
* Remove a log from disk
* @param name The log file name
*/
removeLog(name: string): Promise<void>
removeLog(instancePath: string, name: string): Promise<void>
/**
* Get the log content.
* @param name The log file name
*/
getLogContent(name: string): Promise<string>
getLogContent(instancePath: string, name: string): Promise<string>
/**
* List crash reports in current instance
*/
listCrashReports(): Promise<string[]>
listCrashReports(instancePath: string): Promise<string[]>
/**
* Remove a crash report from disk
* @param name The crash report file name
*/
removeCrashReport(name: string): Promise<void>
removeCrashReport(instancePath: string, name: string): Promise<void>
/**
* Get the crash report content
* @param name The name of crash report
*/
getCrashReportContent(name: string): Promise<string>
getCrashReportContent(instancePath: string, name: string): Promise<string>
/**
* Show the log file on disk. This will open a file explorer.
* @param name The log file name
*/
showLog(name: string): void
showLog(instancePath: string, name: string): void
/**
* Show a crash report on disk. This will open a file explorer.
* @param name The crash report file name
*/
showCrash(name: string): void
showCrash(instancePath: string, name: string): void
}

export const InstanceLogServiceKey: ServiceKey<InstanceLogService> = 'InstanceLogService'

0 comments on commit de4fb64

Please sign in to comment.