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:
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);
}
}
}
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;
}
}
}
}
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:
public async Task LoadDataA() {
await Task.Delay(1000);
Debug.Log("Data loaded");
}
public IEnumerator LoadDataB() {
yield return new WaitForSeconds(1);
Debug.Log("Data loaded");
}
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:
public class PreferenceSaver {
public void SavePreference(string key, string value) {
PlayerPrefs.SetString(key, value);
PlayerPrefs.Save();
}
}
public class SettingsSaver {
public void SaveSetting(string key, string value) {
PlayerPrefs.SetString(key, value);
PlayerPrefs.Save();
}
}
public class SettingsLoader {
public string LoadSetting(string key) {
return PlayerPrefs.GetString(key);
}
}
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:
public void SaveProgress(PlayerProgress progress) {
}
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:
public void ConfigureEnvironment(bool useVR, int quality) {
if (useVR) {
}
}
public void ConfigureVRSettings(int quality) {
}
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:
public struct Player : IComponentData {
}
public struct DefaultInventory : IComponentData {
public int itemCount;
}
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:
public struct VRPlayer : IComponentData { }
public struct AttachToVRPlayer : IComponentData { }
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:
public struct SdkAction : IComponentData {}
public struct SdkActionSystem : ISystem {
public void OnUpdate(ref SystemState state) {
foreach (var (action, entity) in SystemAPI.Query<SdkAction>().WithEntityAccess()) {
Debug.Log("Executing SDK action for entity: " + entity);
}
}
}