Kanda SDK 0.6.0
Loading...
Searching...
No Matches
Design Principles

When contributing to the Kanda SDK, it's important we adhere to certain principles to maintain a high-quality, cohesive, and extensible codebase. The following principles guide our development efforts:

Coherence

We strive for a unified and coherent set of features within the SDK.

Avoid code sprawl

Prevent redundancy and duplication. Aim for a single, unified approach in feature design. If you encounter similar implementations, consider using the existing one or unifying them. Refactor to split functionality into more composable parts if necessary.

Example:

// Bad: Duplicate components with slightly different functionalities
public struct LoggingComponentA : IComponentData { public string Message; }
public struct LoggingComponentB : IComponentData { public string Message; }
public struct LoggingSystemA : ISystem {
public void OnUpdate(ref SystemState state) {
foreach (var (log, entity) in SystemAPI.Query<LoggingComponentA>()) {
Debug.Log("LoggingSystemA: " + log.Message);
}
}
}
public struct LoggingSystemB : ISystem {
public void OnUpdate(ref SystemState state) {
foreach (var (log, entity) in SystemAPI.Query<LoggingComponentB>()) {
Debug.LogWarning("LoggingSystemB Warning: " + log.Message);
}
}
}
// Good: Unify functionalities
public enum LogLevel { Info, Warning, Error }
public struct LoggingComponent : IComponentData {
public string Message;
public LogLevel Level;
}
public struct LoggingSystem : ISystem {
public void OnUpdate(ref SystemState state) {
foreach (var (log, entity) in SystemAPI.Query<LoggingComponent>()) {
switch (log.Level) {
case LogLevel.Info: Debug.Log("Info: " + log.Message); break;
case LogLevel.Warning: Debug.LogWarning("Warning: " + log.Message); break;
case LogLevel.Error: Debug.LogError("Error: " + log.Message); break;
}
}
}
}
// Better: Refactor into smaller, composable parts
public struct LogMessage : IComponentData { public string Message; }
public struct LogLevelComponent : IComponentData { public LogLevel Level; }
public struct LoggingSystemRefactored : ISystem {
public void OnUpdate(ref SystemState state) {
foreach (var (log, level, entity) in SystemAPI.Query<LogMessage, LogLevelComponent>()) {
switch (level.Level) {
case LogLevel.Info: Debug.Log("Info: " + log.Message); break;
case LogLevel.Warning: Debug.LogWarning("Warning: " + log.Message); break;
case LogLevel.Error: Debug.LogError("Error: " + log.Message); break;
}
}
}
}

Consistent Design

Ensure similar design patterns and approaches are used across features. For instance, prefer async-await over coroutines for consistency.

Example:

// Bad: Mixing async-await and coroutines
public async Task LoadDataA() {
await Task.Delay(1000);
Debug.Log("Data loaded");
}
public IEnumerator LoadDataB() {
yield return new WaitForSeconds(1);
Debug.Log("Data loaded");
}
// Good: Consistent use of async-await
public async Task LoadDataA() {
await Task.Delay(1000);
Debug.Log("Data loaded");
}
public async Task LoadDataB() {
await Task.Delay(1000);
Debug.Log("Data loaded");
}

Similar APIs

Ensure that APIs look and feel similar. Familiarity helps developers learn new features quickly. Match the style and conventions of existing systems.

Example:

// Existing class for saving preferences
public class PreferenceSaver {
public void SavePreference(string key, string value) {
PlayerPrefs.SetString(key, value);
PlayerPrefs.Save();
}
}
// New class for saving settings, matching style
public class SettingsSaver {
public void SaveSetting(string key, string value) {
PlayerPrefs.SetString(key, value);
PlayerPrefs.Save();
}
}
// Existing class for loading settings
public class SettingsLoader {
public string LoadSetting(string key) {
return PlayerPrefs.GetString(key);
}
}
// New method for loading preferences, matching style
public class PreferenceLoader {
public string LoadPreference(string key) {
return PlayerPrefs.GetString(key);
}
}

Accessibility

Our SDK is intended for use by external Unity teams who may be unfamiliar with its internal workings. Ensuring ease of use is crucial.

Documentation

Write thorough C# XML docs and summaries. Generate comprehensive scripting API documentation.

Example:

/// <summary>
/// Saves the player's progress.
/// </summary>
/// <param name="progress">The player's progress data.</param>
public void SaveProgress(PlayerProgress progress) {
// Save logic
}

User Manuals

Create concise markdown pages explaining features, their purpose, and usage.

Example:

# Player Progress System
## Overview
The Player Progress System allows you to save and load the player's progress.
## Usage
```csharp
PlayerProgress progress = new PlayerProgress();
progress.Level = 5;
progress.Score = 1000;
ProgressManager.SaveProgress(progress);
```

Simplicity

Design APIs with clear intent, minimizing the need for implicit knowledge to use them effectively.

Example:

// Bad: Complicated API with hidden dependencies
public void ConfigureEnvironment(bool useVR, int quality) {
if (useVR) {
// VR specific configuration
}
// Quality setting logic
}
// Good: Simplified and clear API
public void ConfigureVRSettings(int quality) {
// VR specific configuration
// Quality setting logic
}

Extensibility

We aim to provide an SDK that is easy to extend while maintaining the core codebase's stability and integrity.

Open-Closed Principle

Our SDK code should be closed for modification but open for extension. We manage and maintain the core code, while providing extension points for developers.

Example:

// Core SDK component - content developers don't interact with this
public struct Player : IComponentData {
// Player properties
}
// Default inventory which can be added to a content scene
public struct DefaultInventory : IComponentData {
public int itemCount;
}
// System to that extends Player with DefaultInventory if found
public struct AddDefaultInventorySystem : ISystem {
public void OnUpdate(ref SystemState state) {
if (!SystemAPI.TryGetSingleton<DefaultInventory>(out var defaultInventory)) {
return;
}
foreach (var (player, playerEntity) in SystemAPI.Query<Player>().WithNone<DefaultInventory>().WithEntityAccess()) {
EntityManager.AddComponentData(playerEntity, defaultInventory);
}
}
}

Modular Architecture

Own critical parts of the application flow and main prefabs, but allow for extensions. For example, let content developers place components in the scene that will be automatically discovered and attached to the player by the SDK.

Example:

// Core player component - closed for modification
public struct VRPlayer : IComponentData { }
// Tag component to mark entities for attachment
public struct AttachToVRPlayer : IComponentData { }
// System to discover and attach components at runtime
public struct PlayerAttachmentSystem : ISystem {
public void OnUpdate(ref SystemState state) {
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (attachmentEntity, attachment) in SystemAPI.Query<AttachToVRPlayer>().WithEntityAccess()) {
foreach (var (playerEntity, player) in SystemAPI.Query<VRPlayer>().WithEntityAccess()) {
ecb.AddComponent(attachmentEntity, new Parent { Value = playerEntity });
ecb.AddComponent<LocalToParent>(attachmentEntity);
}
}
ecb.Playback(EntityManager);
ecb.Dispose();
}
}

Authoring Tools

Provide powerful tools that enable developers to create functionality on top of the SDK without requiring new builds. This includes visual scripting, high-level scripting, and state machines.

Example:

// Example of an action component
public struct SdkAction : IComponentData {}
// System to handle actions in the scene
public struct SdkActionSystem : ISystem {
public void OnUpdate(ref SystemState state) {
foreach (var (action, entity) in SystemAPI.Query<SdkAction>().WithEntityAccess()) {
// Execute action logic
Debug.Log("Executing SDK action for entity: " + entity);
}
}
}