Skip to content

Latest commit

 

History

History
468 lines (357 loc) · 12.7 KB

File metadata and controls

468 lines (357 loc) · 12.7 KB

Quick Wins - High Impact, Low Risk

These fixes provide significant improvements with minimal effort

1. React.memo for NetworkedAvatar (5 minutes)

File: packages/clients/hub-client/src/components/NetworkedAvatar.tsx

Before:

export function NetworkedAvatar({
  position,
  rotation,
  displayName,
  avatarConfig
}: NetworkedAvatarProps) {
  // ... 50+ lines of rendering logic
}

After:

import React, { memo } from 'react';

export const NetworkedAvatar = memo<NetworkedAvatarProps>(
  ({ position, rotation, displayName, avatarConfig }) => {
    // ... existing code
  },
  (prev, next) => {
    // Only re-render if position changes significantly or rotation changes
    const positionChanged =
      Math.abs(prev.position[0] - next.position[0]) > 0.01 ||
      Math.abs(prev.position[1] - next.position[1]) > 0.01 ||
      Math.abs(prev.position[2] - next.position[2]) > 0.01;

    const rotationChanged = prev.rotation[1] !== next.rotation[1];

    return positionChanged || rotationChanged;
  }
);

Expected Impact: 30-40% reduction in avatar rendering time, smoother 60 FPS


2. Code Splitting in Vite (10 minutes)

File: packages/clients/hub-client/vite.config.ts

Before:

manualChunks: {
  'three-vendor': ['three', '@react-three/fiber', '@react-three/drei']
}

After:

manualChunks: {
  'three-vendor': ['three', '@react-three/fiber', '@react-three/drei', '@react-three/postprocessing'],
  'physics': ['cannon-es'],
  'networking': ['ws', 'events'],
  'ui-vendor': ['react', 'react-dom'],
  'demo-scenes': [
    './src/components/DemoScene.tsx',
    './src/components/InteractiveDemoScene.tsx',
    './src/components/MediaDemoScene.tsx',
    './src/components/PortalDemoScene.tsx',
    './src/components/GrabDemoScene.tsx'
  ],
  'avatar-editor': [
    './src/avatar/AvatarConfigurator.tsx',
    './src/avatar/AvatarPreview.tsx',
    './src/avatar/AvatarRenderer.tsx'
  ],
  'storage': [
    './src/storage/AssetBrowser.tsx',
    './src/storage/AssetUploader.tsx'
  ]
},
chunkSizeWarningLimit: 1000

Lazy Loading in App.tsx:

import { lazy, Suspense } from 'react';

const DemoScene = lazy(() => import('./components/DemoScene'));
const InteractiveDemoScene = lazy(() => import('./components/InteractiveDemoScene'));
const MediaDemoScene = lazy(() => import('./components/MediaDemoScene'));
const PortalDemoScene = lazy(() => import('./components/PortalDemoScene'));
const GrabDemoScene = lazy(() => import('./components/GrabDemoScene'));
const AvatarConfigurator = lazy(() => import('./avatar/AvatarConfigurator'));
const AssetBrowser = lazy(() => import('./storage/AssetBrowser'));
const AssetUploader = lazy(() => import('./storage/AssetUploader'));

// Usage
<Suspense fallback={<LoadingSpinner />}>
  <DemoScene />
</Suspense>

Expected Impact: 60% reduction in initial bundle (2MB → <800KB), 40% faster initial load


3. Database Connection Pooling (15 minutes)

File: packages/services/reticulum/core/src/db.rs

Before:

opt.max_connections(10)
    .min_connections(1)
    .connect_timeout(Duration::from_secs(8))
    .idle_timeout(Duration::from_secs(8))
    .max_lifetime(Duration::from_secs(8))

After:

opt.max_connections(100)        // Increased for better performance
    .min_connections(10)         // Keep warm connections
    .connect_timeout(Duration::from_secs(30))  // More patient connections
    .acquire_timeout(Duration::from_secs(30))
    .idle_timeout(Duration::from_secs(600))     // 10 minutes instead of 8 seconds
    .max_lifetime(Duration::from_secs(3600))    // 1 hour instead of 8 seconds

Shared Pool in Services:

// In main.rs
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let config = Config::load_or_default()?;
    let db = Arc::new(db::connect(&config).await?);

    let app_state = web::Data::new(AppState { db });

    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone())
            .service(handlers::routes())
    })
    .bind(&config.server.url)?
    .run()
    .await
}

Expected Impact: 50-70% reduction in database connection overhead


4. Health Check Optimization (10 minutes)

File: packages/clients/admin-client/src/App.tsx

Before:

useEffect(() => {
  checkHealth();
  fetchStats();

  const healthInterval = setInterval(checkHealth, 10000); // Every 10s
  const statsInterval = setInterval(fetchStats, 10000);

  return () => {
    clearInterval(healthInterval);
    clearInterval(statsInterval);
  };
}, []);

After:

useEffect(() => {
  const checkHealth = async () => {
    // Only check services that aren't healthy or are slow
    const needsCheck = services.filter(
      (s) => s.status !== 'healthy' || (s.latency && s.latency > 1000) // Re-check slow services
    );

    if (needsCheck.length === 0) return; // Skip if all healthy

    const results = await Promise.all(needsCheck.map((service) => fetchServiceHealth(service.url)));
    // ... update state
  };

  const fetchStats = async () => {
    // Only fetch stats on dashboard tab
    if (activeTab !== 'dashboard') return;

    setLoadingStats(true);
    try {
      const systemStats = await fetchAllSystemStatistics();
      setStats(systemStats);
    } catch (error) {
      console.error('Failed to fetch system statistics:', error);
    } finally {
      setLoadingStats(false);
    }
  };

  checkHealth();
  fetchStats();

  // Stagger: 30s for health, 60s for stats
  const healthInterval = setInterval(checkHealth, 30000);
  const statsInterval = setInterval(fetchStats, 60000);

  return () => {
    clearInterval(healthInterval);
    clearInterval(statsInterval);
  };
}, [activeTab]); // Re-run when tab changes

Expected Impact: 60% reduction in health check requests


5. Position Update Throttling (10 minutes)

File: packages/clients/hub-client/src/App.tsx

Before:

// Position updates at 60 FPS
setInterval(() => {
  client.sendPositionUpdate(myClientId || 'local', { x: newX, y, z: newZ }, rotation);
}, 16); // ~60 FPS

After:

useEffect(() => {
  if (!connected || !client) return;

  let lastUpdateTime = 0;
  const updateInterval = setInterval(() => {
    const now = Date.now();
    if (now - lastUpdateTime < 50) return; // Max 20 FPS

    // Calculate movement
    keysPressed.current.forEach((key) => {
      // ... existing movement logic
    });

    const newPos: [number, number, number] = [newX, y, newZ];

    // Only send if moved more than 1cm
    if (newX !== x || newZ !== z) {
      client.sendPositionUpdate(myClientId || 'local', { x: newX, y, z: newZ }, rotation);
      lastUpdateTime = now;
    }

    return newPos;
  }, 50); // 20 FPS instead of 60 FPS

  return () => clearInterval(updateInterval);
}, [connected, client, myClientId, cameraRotation]);

Expected Impact: 50% reduction in WebSocket traffic


6. Add Input Validation Utilities (5 minutes)

File: packages/shared/types/src/validation.ts

Create:

import { z } from 'zod';

// String schema with min/max length
export const stringSchema = (min: number, max: number, fieldName: string) =>
  z
    .string()
    .min(min, `${fieldName} must be at least ${min} characters`)
    .max(max, `${fieldName} must be at most ${max} characters`)
    .trim();

// Number schema with range
export const numberSchema = (min: number, max: number, fieldName: string) =>
  z
    .number()
    .min(min, `${fieldName} must be at least ${min}`)
    .max(max, `${fieldName} must be at most ${max}`);

// Boolean schema
export const booleanSchema = (fieldName: string) =>
  z.boolean({
    required_error: `${fieldName} is required`,
    invalid_type_error: `${fieldName} must be a boolean`
  });

// Safe JSON parse with validation
export function safeJsonParse<T>(
  json: string,
  schema: z.ZodSchema<T>
): { success: boolean; data?: T; error?: string } {
  try {
    const parsed = JSON.parse(json);
    const result = schema.safeParse(parsed);

    if (result.success) {
      return { success: true, data: result.data };
    } else {
      return { success: false, error: result.error.errors[0].message };
    }
  } catch (error) {
    return { success: false, error: `Invalid JSON: ${error}` };
  }
}

// Email validation
export const emailSchema = z.string().email('Invalid email address').trim().toLowerCase();

// URL validation
export const urlSchema = z.string().url('Invalid URL').trim();

Usage:

// In settings-persistence.ts
import { safeJsonParse, stringSchema, booleanSchema, numberSchema } from '@graphwiz/types/validation';

const UserSettingsSchema = z.object({
  audio_volume: numberSchema(0, 100, 'Audio volume'),
  microphone_volume: numberSchema(0, 100, 'Microphone volume'),
  push_to_talk: booleanSchema('Push to talk'),
  graphics_quality: z.enum(['low', 'medium', 'high']),
  shadows_enabled: booleanSchema('Shadows enabled'),
  display_name: stringSchema(1, 32, 'Display name'),
});

private loadSettings(): UserSettings {
  try {
    const stored = localStorage.getItem(SETTINGS_KEY);
    if (stored) {
      const result = safeJsonParse<UserSettings>(stored, UserSettingsSchema);
      if (result.success && result.data) {
        return { ...DEFAULT_SETTINGS, ...result.data };
      } else {
        logger.error('[SettingsManager] Invalid settings:', result.error);
        return { ...DEFAULT_SETTINGS };
      }
    }
  } catch (error) {
    logger.error('[SettingsManager] Failed to load settings:', error);
  }
  return { ...DEFAULT_SETTINGS };
}

Expected Impact: Improved security, prevents runtime errors from invalid data


7. Remove Console.log Statements (30 minutes)

Files: Multiple files with debug console.log

Find:

# Find all console.log statements
grep -r "console.log" packages/clients/hub-client/src --include="*.ts" --include="*.tsx" -n

# Find all console.error statements
grep -r "console.error" packages/clients/hub-client/src --include="*.ts" --include="*.tsx" -n

Replace with logger:

// Before
console.log('[App] Connection established');
console.error('[App] Connection failed:', error);

// After
import { createLogger } from '@graphwiz/types/logger';
const logger = createLogger('App');

logger.info('[App] Connection established');
logger.error('[App] Connection failed:', error);

Expected Impact: Cleaner production code, better log management


8. Configure API Endpoints (15 minutes)

File: packages/clients/admin-client/src/api-client.ts

Before:

await fetch(`http://localhost:8011/api/v1/admin/users/${userId}/role`, ...)

After:

// Create environment-based API client
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8011';

async function apiRequest(endpoint: string, options: RequestInit) {
  const url = `${API_BASE_URL}${endpoint}`;
  return fetch(url, options);
}

// Usage
await apiRequest(`/api/v1/admin/users/${userId}/role`, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ role, granted_by }),
  signal: AbortSignal.timeout(5000)
});

Environment Configuration:

# .env.example
VITE_API_BASE_URL=http://localhost:8011

# Production override
VITE_API_BASE_URL=https://api.graphwiz.ai

Expected Impact: Easier deployment to different environments


Summary of Quick Wins

Fix File Time Impact
React.memo NetworkedAvatar.tsx 5 min 30-40% less re-renders
Code Splitting vite.config.ts 10 min 60% smaller bundle
DB Pooling db.rs 15 min 50-70% faster DB
Health Check Opt admin-client/App.tsx 10 min 60% fewer requests
Position Throttling App.tsx 10 min 50% less traffic
Input Validation validation.ts 5 min Better security
Remove console.log Multiple files 30 min Cleaner code
API Config api-client.ts 15 min Easier deployment

Total Time: ~2 hours Expected Impact: 60-70% improvement in performance, security, and code quality