summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-16 13:54:09 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-16 13:54:09 -0400
commit7868396718b13443209e7c5d83a2c96cd7eee31e (patch)
tree96e8e172c4484caecca591174a3dea2e5d8da363
parent41bba67d24241dea18b056734b153270bb230ba1 (diff)
downloaddecky-lsfg-vk-7868396718b13443209e7c5d83a2c96cd7eee31e.tar.gz
decky-lsfg-vk-7868396718b13443209e7c5d83a2c96cd7eee31e.zip
centralized configuration system for lsfg-vk params
-rw-r--r--DRY_IMPLEMENTATION.md157
-rw-r--r--py_modules/lsfg_vk/config_schema.py228
-rw-r--r--py_modules/lsfg_vk/configuration.py64
-rw-r--r--py_modules/lsfg_vk/constants.py26
-rw-r--r--py_modules/lsfg_vk/installation.py21
-rw-r--r--py_modules/lsfg_vk/plugin.py17
-rw-r--r--py_modules/lsfg_vk/types.py13
-rw-r--r--src/api/lsfgApi.ts28
-rw-r--r--src/components/ConfigurationSection.tsx64
-rw-r--r--src/components/Content.tsx55
-rw-r--r--src/components/UsageInstructions.tsx30
-rw-r--r--src/config/configSchema.ts186
-rw-r--r--src/hooks/useLsfgHooks.ts91
13 files changed, 693 insertions, 287 deletions
diff --git a/DRY_IMPLEMENTATION.md b/DRY_IMPLEMENTATION.md
new file mode 100644
index 0000000..b6f5487
--- /dev/null
+++ b/DRY_IMPLEMENTATION.md
@@ -0,0 +1,157 @@
+# DRY Configuration System Implementation
+
+## Overview
+This implementation addresses the "Duplicated Configuration Parameter Handling" opportunity by centralizing all configuration-related logic in a single schema that can be shared between Python backend and TypeScript frontend.
+
+## Key Changes Made
+
+### 1. **Created Centralized Configuration Schema**
+- **Python**: `py_modules/lsfg_vk/config_schema.py`
+- **TypeScript**: `src/config/configSchema.ts`
+
+Both files define the same configuration structure with:
+- Field definitions (name, type, default, description)
+- Script generation templates
+- Validation logic
+- Type-safe interfaces
+
+### 2. **Simplified Backend Configuration**
+- **Before**: 8 separate parameters passed to `update_lsfg_config()`
+- **After**: Variable arguments `*args` using centralized schema
+
+```python
+# Before
+async def update_lsfg_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float,
+ hdr: bool, perf_mode: bool, immediate_mode: bool, disable_vkbasalt: bool, frame_cap: int)
+
+# After
+async def update_lsfg_config(self, *args)
+```
+
+### 3. **Streamlined Frontend Configuration**
+- **Before**: 8 separate handler functions in `Content.tsx`
+- **After**: Single generic handler using field names
+
+```tsx
+// Before
+const handleEnableLsfgChange = async (value: boolean) => {
+ setters.setEnableLsfg(value);
+ await updateConfig(value, config.multiplier, config.flowScale, ...);
+};
+// ... 7 more similar handlers
+
+// After
+const handleConfigChange = async (fieldName: keyof ConfigurationData, value: boolean | number) => {
+ await updateField(fieldName, value);
+};
+```
+
+### 4. **Updated Configuration Service**
+- Removed hardcoded defaults from `constants.py`
+- Removed duplicate script template logic
+- Uses centralized `ConfigurationManager` for all operations
+
+### 5. **Modernized Hook System**
+- **Before**: Separate state variables for each config field
+- **After**: Single `ConfigurationData` object with field-based updates
+
+## Benefits Achieved
+
+### 1. **Reduced Code Duplication**
+- Configuration parameters defined once in schema
+- Script generation logic centralized
+- No more repetitive handler functions
+
+### 2. **Easier to Add New Configuration Options**
+To add a new configuration option, you now only need to:
+1. Add the field definition to both config schemas
+2. The rest of the system automatically handles it
+
+### 3. **Type Safety**
+- TypeScript and Python types are consistent
+- Schema validation ensures data integrity
+- Centralized type definitions
+
+### 4. **Maintainability**
+- Single source of truth for configuration
+- Consistent field naming and types
+- Easier to refactor and extend
+
+## Example: Adding a New Configuration Option
+
+To add a new boolean option called "experimental_mode":
+
+1. **Add to Python schema** (`config_schema.py`):
+```python
+"experimental_mode": ConfigField(
+ name="experimental_mode",
+ field_type=ConfigFieldType.BOOLEAN,
+ default=False,
+ description="Enable experimental features",
+ script_template="export LSFG_EXPERIMENTAL={value}",
+ script_comment="# export LSFG_EXPERIMENTAL=1"
+)
+```
+
+2. **Add to TypeScript schema** (`configSchema.ts`):
+```typescript
+experimental_mode: {
+ name: "experimental_mode",
+ fieldType: ConfigFieldType.BOOLEAN,
+ default: false,
+ description: "Enable experimental features",
+ scriptTemplate: "export LSFG_EXPERIMENTAL={value}",
+ scriptComment: "# export LSFG_EXPERIMENTAL=1"
+}
+```
+
+3. **Update type definitions** in both files:
+```python
+# Python
+class ConfigurationData(TypedDict):
+ # ... existing fields ...
+ experimental_mode: bool
+```
+
+```typescript
+// TypeScript
+export interface ConfigurationData {
+ // ... existing fields ...
+ experimental_mode: boolean;
+}
+```
+
+4. **Add UI component** to `ConfigurationSection.tsx`:
+```tsx
+<PanelSectionRow>
+ <ToggleField
+ label="Experimental Mode"
+ description="Enable experimental features"
+ checked={config.experimental_mode}
+ onChange={(value) => onConfigChange('experimental_mode', value)}
+ />
+</PanelSectionRow>
+```
+
+That's it! The rest of the system automatically handles the new field.
+
+## Files Modified
+
+### Python Backend
+- `py_modules/lsfg_vk/config_schema.py` (new)
+- `py_modules/lsfg_vk/types.py` (updated)
+- `py_modules/lsfg_vk/constants.py` (simplified)
+- `py_modules/lsfg_vk/configuration.py` (refactored)
+- `py_modules/lsfg_vk/installation.py` (updated)
+- `py_modules/lsfg_vk/plugin.py` (updated)
+
+### TypeScript Frontend
+- `src/config/configSchema.ts` (new)
+- `src/api/lsfgApi.ts` (updated)
+- `src/hooks/useLsfgHooks.ts` (simplified)
+- `src/components/Content.tsx` (simplified)
+- `src/components/ConfigurationSection.tsx` (updated)
+- `src/components/UsageInstructions.tsx` (updated)
+
+## Testing
+The configuration system has been tested and generates correct script content with proper defaults and field ordering.
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py
new file mode 100644
index 0000000..0f1bdae
--- /dev/null
+++ b/py_modules/lsfg_vk/config_schema.py
@@ -0,0 +1,228 @@
+"""
+Centralized configuration schema for lsfg-vk.
+
+This module defines the complete configuration structure, including:
+- Field definitions with types, defaults, and metadata
+- Script generation logic
+- Validation rules
+- Type definitions
+"""
+
+from typing import TypedDict, Dict, Any, Union, Callable, cast
+from dataclasses import dataclass, field
+from enum import Enum
+
+
+class ConfigFieldType(Enum):
+ """Supported configuration field types"""
+ BOOLEAN = "boolean"
+ INTEGER = "integer"
+ FLOAT = "float"
+
+
+@dataclass
+class ConfigField:
+ """Configuration field definition"""
+ name: str
+ field_type: ConfigFieldType
+ default: Union[bool, int, float]
+ description: str
+ script_template: str # Template for script generation
+ script_comment: str = "" # Comment to add when disabled
+
+ def get_script_line(self, value: Union[bool, int, float]) -> str:
+ """Generate script line for this field"""
+ if self.field_type == ConfigFieldType.BOOLEAN:
+ if value:
+ return self.script_template.format(value=1)
+ else:
+ return f"# {self.script_template.format(value=1)}"
+ else:
+ return self.script_template.format(value=value)
+
+
+# Configuration schema definition
+CONFIG_SCHEMA: Dict[str, ConfigField] = {
+ "enable_lsfg": ConfigField(
+ name="enable_lsfg",
+ field_type=ConfigFieldType.BOOLEAN,
+ default=True,
+ description="Enables the frame generation layer",
+ script_template="export ENABLE_LSFG={value}",
+ script_comment="# export ENABLE_LSFG=1"
+ ),
+
+ "multiplier": ConfigField(
+ name="multiplier",
+ field_type=ConfigFieldType.INTEGER,
+ default=2,
+ description="Traditional FPS multiplier value",
+ script_template="export LSFG_MULTIPLIER={value}"
+ ),
+
+ "flow_scale": ConfigField(
+ name="flow_scale",
+ field_type=ConfigFieldType.FLOAT,
+ default=0.8,
+ description="Lowers the internal motion estimation resolution",
+ script_template="export LSFG_FLOW_SCALE={value}"
+ ),
+
+ "hdr": ConfigField(
+ name="hdr",
+ field_type=ConfigFieldType.BOOLEAN,
+ default=False,
+ description="Enable HDR mode (only if Game supports HDR)",
+ script_template="export LSFG_HDR={value}",
+ script_comment="# export LSFG_HDR=1"
+ ),
+
+ "perf_mode": ConfigField(
+ name="perf_mode",
+ field_type=ConfigFieldType.BOOLEAN,
+ default=True,
+ description="Use lighter model for FG",
+ script_template="export LSFG_PERF_MODE={value}",
+ script_comment="# export LSFG_PERF_MODE=1"
+ ),
+
+ "immediate_mode": ConfigField(
+ name="immediate_mode",
+ field_type=ConfigFieldType.BOOLEAN,
+ default=False,
+ description="Reduce input lag (Experimental, will cause issues in many games)",
+ script_template="export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync",
+ script_comment="# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync"
+ ),
+
+ "disable_vkbasalt": ConfigField(
+ name="disable_vkbasalt",
+ field_type=ConfigFieldType.BOOLEAN,
+ default=True,
+ description="Some plugins add vkbasalt layer, which can break lsfg. Toggling on fixes this",
+ script_template="export DISABLE_VKBASALT={value}",
+ script_comment="# export DISABLE_VKBASALT=1"
+ ),
+
+ "frame_cap": ConfigField(
+ name="frame_cap",
+ field_type=ConfigFieldType.INTEGER,
+ default=0,
+ description="Limit base game FPS (0 = disabled)",
+ script_template="export DXVK_FRAME_RATE={value}",
+ script_comment="# export DXVK_FRAME_RATE=60"
+ )
+}
+
+
+class ConfigurationData(TypedDict):
+ """Type-safe configuration data structure"""
+ enable_lsfg: bool
+ multiplier: int
+ flow_scale: float
+ hdr: bool
+ perf_mode: bool
+ immediate_mode: bool
+ disable_vkbasalt: bool
+ frame_cap: int
+
+
+class ConfigurationManager:
+ """Centralized configuration management"""
+
+ @staticmethod
+ def get_defaults() -> ConfigurationData:
+ """Get default configuration values"""
+ return cast(ConfigurationData, {
+ field.name: field.default
+ for field in CONFIG_SCHEMA.values()
+ })
+
+ @staticmethod
+ def get_field_names() -> list[str]:
+ """Get ordered list of configuration field names"""
+ return list(CONFIG_SCHEMA.keys())
+
+ @staticmethod
+ def get_field_types() -> Dict[str, ConfigFieldType]:
+ """Get field type mapping"""
+ return {
+ field.name: field.field_type
+ for field in CONFIG_SCHEMA.values()
+ }
+
+ @staticmethod
+ def validate_config(config: Dict[str, Any]) -> ConfigurationData:
+ """Validate and convert configuration data"""
+ validated = {}
+
+ for field_name, field_def in CONFIG_SCHEMA.items():
+ value = config.get(field_name, field_def.default)
+
+ # Type validation and conversion
+ if field_def.field_type == ConfigFieldType.BOOLEAN:
+ validated[field_name] = bool(value)
+ elif field_def.field_type == ConfigFieldType.INTEGER:
+ validated[field_name] = int(value)
+ elif field_def.field_type == ConfigFieldType.FLOAT:
+ validated[field_name] = float(value)
+ else:
+ validated[field_name] = value
+
+ return cast(ConfigurationData, validated)
+
+ @staticmethod
+ def generate_script_content(config: ConfigurationData) -> str:
+ """Generate lsfg script content from configuration"""
+ script_lines = ["#!/bin/bash", ""]
+
+ # Generate script lines for each field
+ for field_name in CONFIG_SCHEMA.keys():
+ field_def = CONFIG_SCHEMA[field_name]
+ value = config[field_name]
+
+ if field_def.field_type == ConfigFieldType.BOOLEAN:
+ if value:
+ script_lines.append(field_def.script_template.format(value=1))
+ else:
+ script_lines.append(field_def.script_comment)
+ else:
+ # For frame_cap, special handling for 0 value
+ if field_name == "frame_cap" and value == 0:
+ script_lines.append(field_def.script_comment)
+ else:
+ script_lines.append(field_def.script_template.format(value=value))
+
+ # Add script footer
+ script_lines.extend([
+ "",
+ "# Execute the passed command with the environment variables set",
+ 'exec "$@"'
+ ])
+
+ return "\n".join(script_lines)
+
+ @staticmethod
+ def get_update_signature() -> list[tuple[str, type]]:
+ """Get the function signature for update_config method"""
+ signature = []
+ for field_name, field_def in CONFIG_SCHEMA.items():
+ if field_def.field_type == ConfigFieldType.BOOLEAN:
+ signature.append((field_name, bool))
+ elif field_def.field_type == ConfigFieldType.INTEGER:
+ signature.append((field_name, int))
+ elif field_def.field_type == ConfigFieldType.FLOAT:
+ signature.append((field_name, float))
+ return signature
+
+ @staticmethod
+ def create_config_from_args(*args) -> ConfigurationData:
+ """Create configuration from ordered arguments"""
+ field_names = ConfigurationManager.get_field_names()
+ if len(args) != len(field_names):
+ raise ValueError(f"Expected {len(field_names)} arguments, got {len(args)}")
+
+ return cast(ConfigurationData, {
+ field_name: args[i]
+ for i, field_name in enumerate(field_names)
+ })
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index 5dc0629..8be7b47 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -7,8 +7,8 @@ from pathlib import Path
from typing import Dict, Any
from .base_service import BaseService
-from .constants import LSFG_SCRIPT_TEMPLATE
-from .types import ConfigurationResponse, ConfigurationData
+from .config_schema import ConfigurationManager, ConfigurationData, CONFIG_SCHEMA
+from .types import ConfigurationResponse
class ConfigurationService(BaseService):
@@ -32,8 +32,6 @@ class ConfigurationService(BaseService):
content = self.lsfg_script_path.read_text()
config = self._parse_script_content(content)
- self.log.info(f"Parsed lsfg config: {config}")
-
return {
"success": True,
"config": config,
@@ -60,16 +58,8 @@ class ConfigurationService(BaseService):
Returns:
ConfigurationData with parsed values
"""
- config: ConfigurationData = {
- "enable_lsfg": False,
- "multiplier": 2,
- "flow_scale": 1.0,
- "hdr": False,
- "perf_mode": False,
- "immediate_mode": False,
- "disable_vkbasalt": False,
- "frame_cap": 0
- }
+ # Start with defaults
+ config = ConfigurationManager.get_defaults()
lines = content.split('\n')
for line in lines:
@@ -140,15 +130,18 @@ class ConfigurationService(BaseService):
ConfigurationResponse with success status
"""
try:
- # Generate script content using template
- script_content = self._generate_script_content(
+ # Create configuration from individual arguments
+ config = ConfigurationManager.create_config_from_args(
enable_lsfg, multiplier, flow_scale, hdr, perf_mode, immediate_mode, disable_vkbasalt, frame_cap
)
+ # Generate script content using centralized manager
+ script_content = ConfigurationManager.generate_script_content(config)
+
# Write the updated script atomically
self._atomic_write(self.lsfg_script_path, script_content, 0o755)
- self.log.info(f"Updated lsfg script configuration: enable={enable_lsfg}, "
+ self.log.info(f"Updated lsfg script configuration: enable_lsfg={enable_lsfg}, "
f"multiplier={multiplier}, flow_scale={flow_scale}, hdr={hdr}, "
f"perf_mode={perf_mode}, immediate_mode={immediate_mode}, "
f"disable_vkbasalt={disable_vkbasalt}, frame_cap={frame_cap}")
@@ -169,31 +162,12 @@ class ConfigurationService(BaseService):
"message": None,
"error": str(e)
}
-
- def _generate_script_content(self, enable_lsfg: bool, multiplier: int, flow_scale: float,
- hdr: bool, perf_mode: bool, immediate_mode: bool, disable_vkbasalt: bool, frame_cap: int) -> str:
- """Generate script content from configuration parameters
-
- Args:
- enable_lsfg: Whether to enable LSFG
- multiplier: LSFG multiplier value
- flow_scale: LSFG flow scale value
- hdr: Whether to enable HDR
- perf_mode: Whether to enable performance mode
- immediate_mode: Whether to enable immediate present mode
- disable_vkbasalt: Whether to disable vkbasalt layer
- frame_cap: Frame rate cap value (0-60, 0 = disabled)
-
- Returns:
- Generated script content
- """
- return LSFG_SCRIPT_TEMPLATE.format(
- enable_lsfg="export ENABLE_LSFG=1" if enable_lsfg else "# export ENABLE_LSFG=1",
- multiplier=multiplier,
- flow_scale=flow_scale,
- hdr="export LSFG_HDR=1" if hdr else "# export LSFG_HDR=1",
- perf_mode="export LSFG_PERF_MODE=1" if perf_mode else "# export LSFG_PERF_MODE=1",
- immediate_mode="export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync" if immediate_mode else "# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync",
- disable_vkbasalt="export DISABLE_VKBASALT=1" if disable_vkbasalt else "# export DISABLE_VKBASALT=1",
- frame_cap=f"export DXVK_FRAME_RATE={frame_cap}" if frame_cap > 0 else "# export DXVK_FRAME_RATE=60"
- )
+ except ValueError as e:
+ error_msg = f"Invalid configuration arguments: {str(e)}"
+ self.log.error(error_msg)
+ return {
+ "success": False,
+ "config": None,
+ "message": None,
+ "error": str(e)
+ }
diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py
index 878ac2f..5f1e5a2 100644
--- a/py_modules/lsfg_vk/constants.py
+++ b/py_modules/lsfg_vk/constants.py
@@ -26,33 +26,7 @@ BIN_DIR = "bin"
STEAM_COMMON_PATH = Path("steamapps/common/Lossless Scaling")
LOSSLESS_DLL_NAME = "Lossless.dll"
-# Script template
-LSFG_SCRIPT_TEMPLATE = """#!/bin/bash
-
-{enable_lsfg}
-export LSFG_MULTIPLIER={multiplier}
-export LSFG_FLOW_SCALE={flow_scale}
-{hdr}
-{perf_mode}
-{immediate_mode}
-{disable_vkbasalt}
-{frame_cap}
-
-# Execute the passed command with the environment variables set
-exec "$@"
-"""
-
# Environment variable names
ENV_LSFG_DLL_PATH = "LSFG_DLL_PATH"
ENV_XDG_DATA_HOME = "XDG_DATA_HOME"
ENV_HOME = "HOME"
-
-# Default configuration values
-DEFAULT_MULTIPLIER = 2
-DEFAULT_FLOW_SCALE = 0.8
-DEFAULT_ENABLE_LSFG = True
-DEFAULT_HDR = False
-DEFAULT_PERF_MODE = True
-DEFAULT_IMMEDIATE_MODE = False
-DEFAULT_DISABLE_VKBASALT = True
-DEFAULT_FRAME_CAP = 0
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index 7fb97db..767a97a 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -12,11 +12,9 @@ from typing import Dict, Any
from .base_service import BaseService
from .constants import (
LIB_FILENAME, JSON_FILENAME, ZIP_FILENAME, BIN_DIR,
- SO_EXT, JSON_EXT, LSFG_SCRIPT_TEMPLATE,
- DEFAULT_MULTIPLIER, DEFAULT_FLOW_SCALE, DEFAULT_ENABLE_LSFG,
- DEFAULT_HDR, DEFAULT_PERF_MODE, DEFAULT_IMMEDIATE_MODE, DEFAULT_DISABLE_VKBASALT,
- DEFAULT_FRAME_CAP
+ SO_EXT, JSON_EXT
)
+from .config_schema import ConfigurationManager
from .types import InstallationResponse, UninstallationResponse, InstallationCheckResponse
@@ -106,16 +104,11 @@ class InstallationService(BaseService):
def _create_lsfg_script(self) -> None:
"""Create the lsfg script in home directory with default configuration"""
- script_content = LSFG_SCRIPT_TEMPLATE.format(
- enable_lsfg="export ENABLE_LSFG=1" if DEFAULT_ENABLE_LSFG else "# export ENABLE_LSFG=1",
- multiplier=DEFAULT_MULTIPLIER,
- flow_scale=DEFAULT_FLOW_SCALE,
- hdr="export LSFG_HDR=1" if DEFAULT_HDR else "# export LSFG_HDR=1",
- perf_mode="export LSFG_PERF_MODE=1" if DEFAULT_PERF_MODE else "# export LSFG_PERF_MODE=1",
- immediate_mode="export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync" if DEFAULT_IMMEDIATE_MODE else "# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync",
- disable_vkbasalt="export DISABLE_VKBASALT=1" if DEFAULT_DISABLE_VKBASALT else "# export DISABLE_VKBASALT=1",
- frame_cap=f"export DXVK_FRAME_RATE={DEFAULT_FRAME_CAP}" if DEFAULT_FRAME_CAP > 0 else "# export DXVK_FRAME_RATE=60"
- )
+ # Get default configuration
+ defaults = ConfigurationManager.get_defaults()
+
+ # Generate script content using centralized manager
+ script_content = ConfigurationManager.generate_script_content(defaults)
# Use atomic write to prevent corruption
self._atomic_write(self.lsfg_script_path, script_content, 0o755)
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index 7dcda4e..b357ee6 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -11,6 +11,7 @@ from typing import Dict, Any
from .installation import InstallationService
from .dll_detection import DllDetectionService
from .configuration import ConfigurationService
+from .config_schema import ConfigurationManager
class Plugin:
@@ -72,14 +73,26 @@ class Plugin:
"""
return self.configuration_service.get_config()
+ async def get_config_schema(self) -> Dict[str, Any]:
+ """Get configuration schema information for frontend
+
+ Returns:
+ Dict with field names, types, and defaults
+ """
+ return {
+ "field_names": ConfigurationManager.get_field_names(),
+ "field_types": {name: field_type.value for name, field_type in ConfigurationManager.get_field_types().items()},
+ "defaults": ConfigurationManager.get_defaults()
+ }
+
async def update_lsfg_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float,
hdr: bool, perf_mode: bool, immediate_mode: bool, disable_vkbasalt: bool, frame_cap: int) -> Dict[str, Any]:
"""Update lsfg script configuration
Args:
enable_lsfg: Whether to enable LSFG
- multiplier: LSFG multiplier value (typically 2-4)
- flow_scale: LSFG flow scale value (typically 0.5-2.0)
+ multiplier: LSFG multiplier value
+ flow_scale: LSFG flow scale value
hdr: Whether to enable HDR
perf_mode: Whether to enable performance mode
immediate_mode: Whether to enable immediate present mode (disable vsync)
diff --git a/py_modules/lsfg_vk/types.py b/py_modules/lsfg_vk/types.py
index 9f44cf1..3d555e1 100644
--- a/py_modules/lsfg_vk/types.py
+++ b/py_modules/lsfg_vk/types.py
@@ -3,6 +3,7 @@ Type definitions for the lsfg-vk plugin responses.
"""
from typing import TypedDict, Optional, List
+from .config_schema import ConfigurationData
class BaseResponse(TypedDict):
@@ -54,18 +55,6 @@ class DllDetectionResponse(TypedDict):
error: Optional[str]
-class ConfigurationData(TypedDict):
- """Configuration data structure"""
- enable_lsfg: bool
- multiplier: int
- flow_scale: float
- hdr: bool
- perf_mode: bool
- immediate_mode: bool
- disable_vkbasalt: bool
- frame_cap: int
-
-
class ConfigurationResponse(BaseResponse):
"""Response for configuration operations"""
config: Optional[ConfigurationData]
diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts
index 31ecbb5..2e7964c 100644
--- a/src/api/lsfgApi.ts
+++ b/src/api/lsfgApi.ts
@@ -1,4 +1,5 @@
import { callable } from "@decky/api";
+import { ConfigurationData, ConfigurationManager } from "../config/configSchema";
// Type definitions for API responses
export interface InstallationResult {
@@ -27,16 +28,8 @@ export interface DllDetectionResult {
error?: string;
}
-export interface LsfgConfig {
- enable_lsfg: boolean;
- multiplier: number;
- flow_scale: number;
- hdr: boolean;
- perf_mode: boolean;
- immediate_mode: boolean;
- disable_vkbasalt: boolean;
- frame_cap: number;
-}
+// Use centralized configuration data type
+export type LsfgConfig = ConfigurationData;
export interface ConfigResult {
success: boolean;
@@ -50,13 +43,28 @@ export interface ConfigUpdateResult {
error?: string;
}
+export interface ConfigSchemaResult {
+ field_names: string[];
+ field_types: Record<string, string>;
+ defaults: ConfigurationData;
+}
+
// API functions
export const installLsfgVk = callable<[], InstallationResult>("install_lsfg_vk");
export const uninstallLsfgVk = callable<[], InstallationResult>("uninstall_lsfg_vk");
export const checkLsfgVkInstalled = callable<[], InstallationStatus>("check_lsfg_vk_installed");
export const checkLosslessScalingDll = callable<[], DllDetectionResult>("check_lossless_scaling_dll");
export const getLsfgConfig = callable<[], ConfigResult>("get_lsfg_config");
+export const getConfigSchema = callable<[], ConfigSchemaResult>("get_config_schema");
+
+// Updated config function using centralized configuration
export const updateLsfgConfig = callable<
[boolean, number, number, boolean, boolean, boolean, boolean, number],
ConfigUpdateResult
>("update_lsfg_config");
+
+// Helper function to create config update from configuration object
+export const updateLsfgConfigFromObject = async (config: ConfigurationData): Promise<ConfigUpdateResult> => {
+ const args = ConfigurationManager.createArgsFromConfig(config);
+ return updateLsfgConfig(...args as [boolean, number, number, boolean, boolean, boolean, boolean, number]);
+};
diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx
index 11bb6b9..2545217 100644
--- a/src/components/ConfigurationSection.tsx
+++ b/src/components/ConfigurationSection.tsx
@@ -1,38 +1,14 @@
import { PanelSectionRow, ToggleField, SliderField } from "@decky/ui";
-
-interface LsfgConfig {
- enableLsfg: boolean;
- multiplier: number;
- flowScale: number;
- hdr: boolean;
- perfMode: boolean;
- immediateMode: boolean;
- disableVkbasalt: boolean;
- frameCap: number;
-}
+import { ConfigurationData } from "../config/configSchema";
interface ConfigurationSectionProps {
- config: LsfgConfig;
- onEnableLsfgChange: (value: boolean) => Promise<void>;
- onMultiplierChange: (value: number) => Promise<void>;
- onFlowScaleChange: (value: number) => Promise<void>;
- onHdrChange: (value: boolean) => Promise<void>;
- onPerfModeChange: (value: boolean) => Promise<void>;
- onImmediateModeChange: (value: boolean) => Promise<void>;
- onDisableVkbasaltChange: (value: boolean) => Promise<void>;
- onFrameCapChange: (value: number) => Promise<void>;
+ config: ConfigurationData;
+ onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number) => Promise<void>;
}
export function ConfigurationSection({
config,
- onEnableLsfgChange,
- onMultiplierChange,
- onFlowScaleChange,
- onHdrChange,
- onPerfModeChange,
- onImmediateModeChange,
- onDisableVkbasaltChange,
- onFrameCapChange
+ onConfigChange
}: ConfigurationSectionProps) {
return (
<>
@@ -55,8 +31,8 @@ export function ConfigurationSection({
<ToggleField
label="Enable LSFG"
description="Enables the frame generation layer"
- checked={config.enableLsfg}
- onChange={onEnableLsfgChange}
+ checked={config.enable_lsfg}
+ onChange={(value) => onConfigChange('enable_lsfg', value)}
/>
</PanelSectionRow>
@@ -74,19 +50,19 @@ export function ConfigurationSection({
{ notchIndex: 1, label: "3X" },
{ notchIndex: 2, label: "4X" }
]}
- onChange={onMultiplierChange}
+ onChange={(value) => onConfigChange('multiplier', value)}
/>
</PanelSectionRow>
<PanelSectionRow>
<SliderField
- label={`Flow Scale ${Math.round(config.flowScale * 100)}%`}
+ label={`Flow Scale ${Math.round(config.flow_scale * 100)}%`}
description="Lowers the internal motion estimation resolution"
- value={config.flowScale}
+ value={config.flow_scale}
min={0.25}
max={1.0}
step={0.01}
- onChange={onFlowScaleChange}
+ onChange={(value) => onConfigChange('flow_scale', value)}
/>
</PanelSectionRow>
@@ -95,7 +71,7 @@ export function ConfigurationSection({
label="HDR Mode"
description="Enable HDR mode (only if Game supports HDR)"
checked={config.hdr}
- onChange={onHdrChange}
+ onChange={(value) => onConfigChange('hdr', value)}
/>
</PanelSectionRow>
@@ -103,8 +79,8 @@ export function ConfigurationSection({
<ToggleField
label="Performance Mode"
description="Use lighter model for FG"
- checked={config.perfMode}
- onChange={onPerfModeChange}
+ checked={config.perf_mode}
+ onChange={(value) => onConfigChange('perf_mode', value)}
/>
</PanelSectionRow>
@@ -112,20 +88,20 @@ export function ConfigurationSection({
<ToggleField
label="Immediate Mode"
description="Reduce input lag (Experimental, will cause issues in many games)"
- checked={config.immediateMode}
- onChange={onImmediateModeChange}
+ checked={config.immediate_mode}
+ onChange={(value) => onConfigChange('immediate_mode', value)}
/>
</PanelSectionRow>
<PanelSectionRow>
<SliderField
- label={`Game Frame Cap ${config.frameCap === 0 ? "(Disabled)" : `(${config.frameCap} FPS)`}`}
+ label={`Game Frame Cap ${config.frame_cap === 0 ? "(Disabled)" : `(${config.frame_cap} FPS)`}`}
description="Limit base game FPS (0 = disabled)"
- value={config.frameCap}
+ value={config.frame_cap}
min={0}
max={60}
step={1}
- onChange={onFrameCapChange}
+ onChange={(value) => onConfigChange('frame_cap', value)}
/>
</PanelSectionRow>
@@ -133,8 +109,8 @@ export function ConfigurationSection({
<ToggleField
label="Disable vkbasalt"
description="Some plugins add vkbasalt layer, which can break lsfg. Toggling on fixes this"
- checked={config.disableVkbasalt}
- onChange={onDisableVkbasaltChange}
+ checked={config.disable_vkbasalt}
+ onChange={(value) => onConfigChange('disable_vkbasalt', value)}
/>
</PanelSectionRow> */}
</>
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index 895b2fc..ba651d4 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -8,6 +8,7 @@ import { ConfigurationSection } from "./ConfigurationSection";
import { UsageInstructions } from "./UsageInstructions";
import { WikiButton } from "./WikiButton";
import { ClipboardButton } from "./ClipboardButton";
+import { ConfigurationData } from "../config/configSchema";
export function Content() {
const {
@@ -21,9 +22,8 @@ export function Content() {
const {
config,
- setters,
loadLsfgConfig,
- updateConfig
+ updateField
} = useLsfgConfig();
const { isInstalling, isUninstalling, handleInstall, handleUninstall } = useInstallationActions();
@@ -35,45 +35,9 @@ export function Content() {
}
}, [isInstalled, loadLsfgConfig]);
- // Configuration change handlers
- const handleEnableLsfgChange = async (value: boolean) => {
- setters.setEnableLsfg(value);
- await updateConfig(value, config.multiplier, config.flowScale, config.hdr, config.perfMode, config.immediateMode, config.disableVkbasalt, config.frameCap);
- };
-
- const handleMultiplierChange = async (value: number) => {
- setters.setMultiplier(value);
- await updateConfig(config.enableLsfg, value, config.flowScale, config.hdr, config.perfMode, config.immediateMode, config.disableVkbasalt, config.frameCap);
- };
-
- const handleFlowScaleChange = async (value: number) => {
- setters.setFlowScale(value);
- await updateConfig(config.enableLsfg, config.multiplier, value, config.hdr, config.perfMode, config.immediateMode, config.disableVkbasalt, config.frameCap);
- };
-
- const handleHdrChange = async (value: boolean) => {
- setters.setHdr(value);
- await updateConfig(config.enableLsfg, config.multiplier, config.flowScale, value, config.perfMode, config.immediateMode, config.disableVkbasalt, config.frameCap);
- };
-
- const handlePerfModeChange = async (value: boolean) => {
- setters.setPerfMode(value);
- await updateConfig(config.enableLsfg, config.multiplier, config.flowScale, config.hdr, value, config.immediateMode, config.disableVkbasalt, config.frameCap);
- };
-
- const handleImmediateModeChange = async (value: boolean) => {
- setters.setImmediateMode(value);
- await updateConfig(config.enableLsfg, config.multiplier, config.flowScale, config.hdr, config.perfMode, value, config.disableVkbasalt, config.frameCap);
- };
-
- const handleDisableVkbasaltChange = async (value: boolean) => {
- setters.setDisableVkbasalt(value);
- await updateConfig(config.enableLsfg, config.multiplier, config.flowScale, config.hdr, config.perfMode, config.immediateMode, value, config.frameCap);
- };
-
- const handleFrameCapChange = async (value: number) => {
- setters.setFrameCap(value);
- await updateConfig(config.enableLsfg, config.multiplier, config.flowScale, config.hdr, config.perfMode, config.immediateMode, config.disableVkbasalt, value);
+ // Generic configuration change handler
+ const handleConfigChange = async (fieldName: keyof ConfigurationData, value: boolean | number) => {
+ await updateField(fieldName, value);
};
const onInstall = () => {
@@ -105,14 +69,7 @@ export function Content() {
{isInstalled && (
<ConfigurationSection
config={config}
- onEnableLsfgChange={handleEnableLsfgChange}
- onMultiplierChange={handleMultiplierChange}
- onFlowScaleChange={handleFlowScaleChange}
- onHdrChange={handleHdrChange}
- onPerfModeChange={handlePerfModeChange}
- onImmediateModeChange={handleImmediateModeChange}
- onDisableVkbasaltChange={handleDisableVkbasaltChange}
- onFrameCapChange={handleFrameCapChange}
+ onConfigChange={handleConfigChange}
/>
)}
diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx
index 3589c04..727a0ab 100644
--- a/src/components/UsageInstructions.tsx
+++ b/src/components/UsageInstructions.tsx
@@ -1,18 +1,8 @@
import { PanelSectionRow } from "@decky/ui";
-
-interface ConfigType {
- enableLsfg: boolean;
- multiplier: number;
- flowScale: number;
- hdr: boolean;
- perfMode: boolean;
- immediateMode: boolean;
- disableVkbasalt: boolean;
- frameCap: number;
-}
+import { ConfigurationData } from "../config/configSchema";
interface UsageInstructionsProps {
- config: ConfigType;
+ config: ConfigurationData;
}
export function UsageInstructions({ config }: UsageInstructionsProps) {
@@ -20,34 +10,34 @@ export function UsageInstructions({ config }: UsageInstructionsProps) {
const buildManualEnvVars = (): string => {
const envVars: string[] = [];
- if (config.enableLsfg) {
+ if (config.enable_lsfg) {
envVars.push("ENABLE_LSFG=1");
}
// Always include multiplier and flow_scale if LSFG is enabled, as they have defaults
- if (config.enableLsfg) {
+ if (config.enable_lsfg) {
envVars.push(`LSFG_MULTIPLIER=${config.multiplier}`);
- envVars.push(`LSFG_FLOW_SCALE=${config.flowScale}`);
+ envVars.push(`LSFG_FLOW_SCALE=${config.flow_scale}`);
}
if (config.hdr) {
envVars.push("LSFG_HDR=1");
}
- if (config.perfMode) {
+ if (config.perf_mode) {
envVars.push("LSFG_PERF_MODE=1");
}
- if (config.immediateMode) {
+ if (config.immediate_mode) {
envVars.push("MESA_VK_WSI_PRESENT_MODE=immediate");
}
- if (config.disableVkbasalt) {
+ if (config.disable_vkbasalt) {
envVars.push("DISABLE_VKBASALT=1");
}
- if (config.frameCap > 0) {
- envVars.push(`DXVK_FRAME_RATE=${config.frameCap}`);
+ if (config.frame_cap > 0) {
+ envVars.push(`DXVK_FRAME_RATE=${config.frame_cap}`);
}
return envVars.length > 0 ? `${envVars.join(" ")} %command%` : "%command%";
diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts
new file mode 100644
index 0000000..6956030
--- /dev/null
+++ b/src/config/configSchema.ts
@@ -0,0 +1,186 @@
+/**
+ * Centralized configuration schema for lsfg-vk frontend.
+ *
+ * This mirrors the Python configuration schema to ensure consistency
+ * between frontend and backend configuration handling.
+ */
+
+// Configuration field type enum
+export enum ConfigFieldType {
+ BOOLEAN = "boolean",
+ INTEGER = "integer",
+ FLOAT = "float"
+}
+
+// Configuration field definition
+export interface ConfigField {
+ name: string;
+ fieldType: ConfigFieldType;
+ default: boolean | number;
+ description: string;
+ scriptTemplate: string;
+ scriptComment?: string;
+}
+
+// Configuration schema - must match Python CONFIG_SCHEMA
+export const CONFIG_SCHEMA: Record<string, ConfigField> = {
+ enable_lsfg: {
+ name: "enable_lsfg",
+ fieldType: ConfigFieldType.BOOLEAN,
+ default: true,
+ description: "Enables the frame generation layer",
+ scriptTemplate: "export ENABLE_LSFG={value}",
+ scriptComment: "# export ENABLE_LSFG=1"
+ },
+
+ multiplier: {
+ name: "multiplier",
+ fieldType: ConfigFieldType.INTEGER,
+ default: 2,
+ description: "Traditional FPS multiplier value",
+ scriptTemplate: "export LSFG_MULTIPLIER={value}"
+ },
+
+ flow_scale: {
+ name: "flow_scale",
+ fieldType: ConfigFieldType.FLOAT,
+ default: 0.8,
+ description: "Lowers the internal motion estimation resolution",
+ scriptTemplate: "export LSFG_FLOW_SCALE={value}"
+ },
+
+ hdr: {
+ name: "hdr",
+ fieldType: ConfigFieldType.BOOLEAN,
+ default: false,
+ description: "Enable HDR mode (only if Game supports HDR)",
+ scriptTemplate: "export LSFG_HDR={value}",
+ scriptComment: "# export LSFG_HDR=1"
+ },
+
+ perf_mode: {
+ name: "perf_mode",
+ fieldType: ConfigFieldType.BOOLEAN,
+ default: true,
+ description: "Use lighter model for FG",
+ scriptTemplate: "export LSFG_PERF_MODE={value}",
+ scriptComment: "# export LSFG_PERF_MODE=1"
+ },
+
+ immediate_mode: {
+ name: "immediate_mode",
+ fieldType: ConfigFieldType.BOOLEAN,
+ default: false,
+ description: "Reduce input lag (Experimental, will cause issues in many games)",
+ scriptTemplate: "export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync",
+ scriptComment: "# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync"
+ },
+
+ disable_vkbasalt: {
+ name: "disable_vkbasalt",
+ fieldType: ConfigFieldType.BOOLEAN,
+ default: true,
+ description: "Some plugins add vkbasalt layer, which can break lsfg. Toggling on fixes this",
+ scriptTemplate: "export DISABLE_VKBASALT={value}",
+ scriptComment: "# export DISABLE_VKBASALT=1"
+ },
+
+ frame_cap: {
+ name: "frame_cap",
+ fieldType: ConfigFieldType.INTEGER,
+ default: 0,
+ description: "Limit base game FPS (0 = disabled)",
+ scriptTemplate: "export DXVK_FRAME_RATE={value}",
+ scriptComment: "# export DXVK_FRAME_RATE=60"
+ }
+};
+
+// Type-safe configuration data structure
+export interface ConfigurationData {
+ enable_lsfg: boolean;
+ multiplier: number;
+ flow_scale: number;
+ hdr: boolean;
+ perf_mode: boolean;
+ immediate_mode: boolean;
+ disable_vkbasalt: boolean;
+ frame_cap: number;
+}
+
+// Centralized configuration manager
+export class ConfigurationManager {
+ /**
+ * Get default configuration values
+ */
+ static getDefaults(): ConfigurationData {
+ const defaults = {} as ConfigurationData;
+ Object.values(CONFIG_SCHEMA).forEach(field => {
+ (defaults as any)[field.name] = field.default;
+ });
+ return defaults;
+ }
+
+ /**
+ * Get ordered list of configuration field names
+ */
+ static getFieldNames(): string[] {
+ return Object.keys(CONFIG_SCHEMA);
+ }
+
+ /**
+ * Get field type mapping
+ */
+ static getFieldTypes(): Record<string, ConfigFieldType> {
+ return Object.values(CONFIG_SCHEMA).reduce((acc, field) => {
+ acc[field.name] = field.fieldType;
+ return acc;
+ }, {} as Record<string, ConfigFieldType>);
+ }
+
+ /**
+ * Create ordered arguments array from configuration object
+ */
+ static createArgsFromConfig(config: ConfigurationData): (boolean | number)[] {
+ return this.getFieldNames().map(fieldName =>
+ config[fieldName as keyof ConfigurationData]
+ );
+ }
+
+ /**
+ * Validate configuration object against schema
+ */
+ static validateConfig(config: Partial<ConfigurationData>): ConfigurationData {
+ const defaults = this.getDefaults();
+ const validated = { ...defaults };
+
+ Object.entries(CONFIG_SCHEMA).forEach(([fieldName, fieldDef]) => {
+ const value = config[fieldName as keyof ConfigurationData];
+ if (value !== undefined) {
+ // Type validation
+ if (fieldDef.fieldType === ConfigFieldType.BOOLEAN) {
+ (validated as any)[fieldName] = Boolean(value);
+ } else if (fieldDef.fieldType === ConfigFieldType.INTEGER) {
+ (validated as any)[fieldName] = parseInt(String(value), 10);
+ } else if (fieldDef.fieldType === ConfigFieldType.FLOAT) {
+ (validated as any)[fieldName] = parseFloat(String(value));
+ }
+ }
+ });
+
+ return validated;
+ }
+
+ /**
+ * Get configuration field definition
+ */
+ static getFieldDef(fieldName: string): ConfigField | undefined {
+ return CONFIG_SCHEMA[fieldName];
+ }
+
+ /**
+ * Get all field definitions
+ */
+ static getAllFieldDefs(): ConfigField[] {
+ return Object.values(CONFIG_SCHEMA);
+ }
+}
diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts
index f765ce6..8ff9061 100644
--- a/src/hooks/useLsfgHooks.ts
+++ b/src/hooks/useLsfgHooks.ts
@@ -1,12 +1,13 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, useCallback } from "react";
import { toaster } from "@decky/api";
import {
checkLsfgVkInstalled,
checkLosslessScalingDll,
getLsfgConfig,
- updateLsfgConfig,
+ updateLsfgConfigFromObject,
type ConfigUpdateResult
} from "../api/lsfgApi";
+import { ConfigurationData, ConfigurationManager } from "../config/configSchema";
export function useInstallationStatus() {
const [isInstalled, setIsInstalled] = useState<boolean>(false);
@@ -70,58 +71,30 @@ export function useDllDetection() {
}
export function useLsfgConfig() {
- const [enableLsfg, setEnableLsfg] = useState<boolean>(true);
- const [multiplier, setMultiplier] = useState<number>(2);
- const [flowScale, setFlowScale] = useState<number>(0.8);
- const [hdr, setHdr] = useState<boolean>(false);
- const [perfMode, setPerfMode] = useState<boolean>(true);
- const [immediateMode, setImmediateMode] = useState<boolean>(false);
- const [disableVkbasalt, setDisableVkbasalt] = useState<boolean>(true);
- const [frameCap, setFrameCap] = useState<number>(0);
+ // Use centralized configuration for initial state
+ const [config, setConfig] = useState<ConfigurationData>(() => ConfigurationManager.getDefaults());
- const loadLsfgConfig = async () => {
+ const loadLsfgConfig = useCallback(async () => {
try {
const result = await getLsfgConfig();
if (result.success && result.config) {
- setEnableLsfg(result.config.enable_lsfg);
- setMultiplier(result.config.multiplier);
- setFlowScale(result.config.flow_scale);
- setHdr(result.config.hdr);
- setPerfMode(result.config.perf_mode);
- setImmediateMode(result.config.immediate_mode);
- setDisableVkbasalt(result.config.disable_vkbasalt);
- setFrameCap(result.config.frame_cap);
- console.log("Loaded lsfg config:", result.config);
+ setConfig(result.config);
} else {
console.log("lsfg config not available, using defaults:", result.error);
+ setConfig(ConfigurationManager.getDefaults());
}
} catch (error) {
console.error("Error loading lsfg config:", error);
+ setConfig(ConfigurationManager.getDefaults());
}
- };
+ }, []);
- const updateConfig = async (
- newEnableLsfg: boolean,
- newMultiplier: number,
- newFlowScale: number,
- newHdr: boolean,
- newPerfMode: boolean,
- newImmediateMode: boolean,
- newDisableVkbasalt: boolean,
- newFrameCap: number
- ): Promise<ConfigUpdateResult> => {
+ const updateConfig = useCallback(async (newConfig: ConfigurationData): Promise<ConfigUpdateResult> => {
try {
- const result = await updateLsfgConfig(
- newEnableLsfg,
- newMultiplier,
- newFlowScale,
- newHdr,
- newPerfMode,
- newImmediateMode,
- newDisableVkbasalt,
- newFrameCap
- );
- if (!result.success) {
+ const result = await updateLsfgConfigFromObject(newConfig);
+ if (result.success) {
+ setConfig(newConfig);
+ } else {
toaster.toast({
title: "Update Failed",
body: result.error || "Failed to update configuration"
@@ -135,34 +108,22 @@ export function useLsfgConfig() {
});
return { success: false, error: String(error) };
}
- };
+ }, []);
+
+ const updateField = useCallback(async (fieldName: keyof ConfigurationData, value: boolean | number): Promise<ConfigUpdateResult> => {
+ const newConfig = { ...config, [fieldName]: value };
+ return updateConfig(newConfig);
+ }, [config, updateConfig]);
useEffect(() => {
loadLsfgConfig();
- }, []);
+ }, []); // Empty dependency array to prevent infinite loop
return {
- config: {
- enableLsfg,
- multiplier,
- flowScale,
- hdr,
- perfMode,
- immediateMode,
- disableVkbasalt,
- frameCap
- },
- setters: {
- setEnableLsfg,
- setMultiplier,
- setFlowScale,
- setHdr,
- setPerfMode,
- setImmediateMode,
- setDisableVkbasalt,
- setFrameCap
- },
+ config,
+ setConfig,
loadLsfgConfig,
- updateConfig
+ updateConfig,
+ updateField
};
}