Skip to content

Commit

Permalink
perf: ⚡️ Optimize scrcpy parameter conversion performance
Browse files Browse the repository at this point in the history
  • Loading branch information
viarotel committed Oct 23, 2024
1 parent ca79e1b commit 18dcd24
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 54 deletions.
34 changes: 22 additions & 12 deletions electron/exposes/scrcpy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import util from 'node:util'
import { adbPath, scrcpyPath } from '$electron/configs/index.js'
import appStore from '$electron/helpers/store.js'
import { replaceIP, sleep } from '$renderer/utils/index.js'
import commandHelper from '$renderer/utils/command/index.js'

let adbkit

const exec = util.promisify(_exec)

const shell = async (command, { stdout, stderr } = {}) => {
async function shell(command, { stdout, stderr } = {}) {
const spawnPath = appStore.get('common.scrcpyPath') || scrcpyPath
const ADB = appStore.get('common.adbPath') || adbPath
const args = command.split(' ')
Expand Down Expand Up @@ -58,7 +59,7 @@ const shell = async (command, { stdout, stderr } = {}) => {
})
}

const execShell = async (command) => {
async function execShell(command) {
const spawnPath = appStore.get('common.scrcpyPath') || scrcpyPath
const ADB = appStore.get('common.adbPath') || adbPath

Expand All @@ -71,7 +72,7 @@ const execShell = async (command) => {
return res
}

const getEncoders = async (serial) => {
async function getEncoders(serial) {
const res = await execShell(`--serial="${serial}" --list-encoders`)
const stdout = res.stdout

Expand All @@ -97,29 +98,26 @@ const getEncoders = async (serial) => {
return value
}

const mirror = async (
async function mirror(
serial,
{ title, args = '', exec = false, ...options } = {},
) => {
const mirrorShell = exec ? execShell : shell
) {
const currentShell = exec ? execShell : shell

return mirrorShell(
return currentShell(
`--serial="${serial}" --window-title="${title}" ${args}`,
options,
)
}

const record = async (
serial,
{ title, args = '', savePath, ...options } = {},
) => {
async function record(serial, { title, args = '', savePath, ...options } = {}) {
return shell(
`--serial="${serial}" --window-title="${title}" --record="${savePath}" ${args}`,
options,
)
}

const mirrorGroup = async (serial, { openNum = 1, ...options } = {}) => {
async function mirrorGroup(serial, { openNum = 1, ...options } = {}) {
const overlayDisplay
= appStore.get(`scrcpy.${replaceIP(serial)}.--display-overlay`)
|| appStore.get('scrcpy.global.--display-overlay')
Expand Down Expand Up @@ -170,6 +168,17 @@ const mirrorGroup = async (serial, { openNum = 1, ...options } = {}) => {
return Promise.allSettled(results)
}

async function control(serial, { command, exec = true, ...options } = {}) {
const currentShell = exec ? execShell : shell

const stringCommand = commandHelper.stringify(command)

return currentShell(
`--serial="${serial}" --no-video --no-audio ${stringCommand}`,
options,
)
}

export default (options = {}) => {
adbkit = options.adbkit

Expand All @@ -180,5 +189,6 @@ export default (options = {}) => {
mirror,
record,
mirrorGroup,
control,
}
}
62 changes: 20 additions & 42 deletions src/store/preference/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
} from './helpers/index.js'
import model from './model/index.js'

import command from '$/utils/command/index.js'

const { adbPath, scrcpyPath, gnirehtetPath } = window.electron?.configs || {}

export const usePreferenceStore = defineStore({
Expand Down Expand Up @@ -130,7 +132,7 @@ export const usePreferenceStore = defineStore({
return value
},

getScrcpyArgs(
scrcpyParameter(
scope = this.deviceScope,
{ isRecord = false, isCamera = false, isOtg = false, excludes = [] } = {},
) {
Expand All @@ -140,55 +142,31 @@ export const usePreferenceStore = defineStore({
return ''
}

const valueList = Object.entries(data).reduce((arr, [key, value]) => {
if (!value && typeof value !== 'number') {
return arr
const params = Object.entries(data).reduce((obj, [key, value]) => {
const shouldExclude
= (!value && typeof value !== 'number')
|| this.excludeKeys.includes(key)
|| (!isRecord && this.recordKeys.includes(key))
|| (!isCamera && this.cameraKeys.includes(key))
|| (!isOtg && this.otgKeys.includes(key))
|| excludes.includes(key)
|| excludes.includes(`${key}=${value}`)

if (shouldExclude) {
return obj
}

if (this.excludeKeys.includes(key)) {
return arr
}
obj[key] = value

if (!isRecord) {
if (this.recordKeys.includes(key)) {
return arr
}
}
return obj
}, {})

if (!isCamera) {
if (this.cameraKeys.includes(key)) {
return arr
}
}

if (!isOtg) {
if (this.otgKeys.includes(key)) {
return arr
}
}

if (excludes.includes(key) || excludes.includes(`${key}=${value}`)) {
return arr
}

if (typeof value === 'boolean') {
arr.push(key)
}
else {
arr.push(`${key}="${value}"`)
}

return arr
}, [])
let value = command.stringify(params)

if (data.scrcpyAppend) {
valueList.push(...data.scrcpyAppend.split(' '))
value += ` ${data.scrcpyAppend}`
}

const value = valueList.join(' ')

// console.log('value', value)

return value
},
getModel(path) {
Expand Down
109 changes: 109 additions & 0 deletions src/utils/command/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Convert object parameters to command line arguments string
* @param {Object} options - The options object containing parameters
* @returns {string} The formatted command line arguments string
*/
function stringify(options) {
if (!options || typeof options !== 'object' || Array.isArray(options)) {
console.warn('Options must be a plain object')
return ''
}

const args = []

// Helper function to format parameter names
const formatParamName = (name) => {
// 验证参数名称的合法性
if (typeof name !== 'string' || !name.length) {
throw new TypeError('Parameter name must be a non-empty string')
}

if (name.startsWith('-')) {
return name
}

return name.length === 1
? `-${name}`
: `--${name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}`
}

// Helper function to format values based on their types
const formatValue = (value) => {
if (value === null || value === undefined) {
throw new TypeError('Value cannot be null or undefined')
}

if (typeof value === 'string') {
// 处理空字符串
if (!value.length) {
return '""'
}
// 转义引号并在需要时添加引号
const needsQuotes = /[\s"']/.test(value)
return needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value
}

if (typeof value === 'number') {
if (!Number.isFinite(value)) {
throw new TypeError('Number values must be finite')
}
return value.toString()
}

if (typeof value === 'boolean') {
return '' // 布尔值不需要返回值
}

if (Array.isArray(value)) {
return formatValue(value.join(','))
}

throw new TypeError(`Unsupported value type: ${typeof value}`)
}

// Process each option
for (const [key, value] of Object.entries(options)) {
// Skip null or undefined values
if ([null, undefined, false].includes(value)) {
continue
}

const paramName = formatParamName(key)

// Handle boolean flags
if (typeof value === 'boolean') {
if (value) {
args.push(paramName)
}
continue
}

// Handle array values
if (Array.isArray(value)) {
if (value.length === 0) {
continue // 跳过空数组
}
value.forEach((item) => {
if (item !== null && item !== undefined) {
const formattedValue = formatValue(item)
if (formattedValue) {
args.push(`${paramName} ${formattedValue}`)
}
}
})
continue
}

// Handle regular key-value pairs
const formattedValue = formatValue(value)
if (formattedValue) {
args.push(`${paramName}=${formattedValue}`)
}
}

return args.join(' ')
}

export { stringify }

export default { stringify }

0 comments on commit 18dcd24

Please sign in to comment.