DEV Community

David De La Cruz Morales
David De La Cruz Morales

Posted on

Reporte Técnico: Desarrollo de un Simulador de Estacionamiento Concurrente en Go

Introducción

Este proyecto consiste en un simulador de estacionamiento concurrente desarrollado en Go, utilizando la librería gráfica Fyne para la interfaz de usuario. Su objetivo es modelar el comportamiento de un estacionamiento en tiempo real, gestionando la entrada y salida de vehículos de forma concurrente y mostrando visualmente el estado actualizado de los cajones de estacionamiento.
El proyecto combina los conceptos de concurrencia, el patrón de diseño Observer y la renderización dinámica en una interfaz gráfica. Este reporte detalla el uso de estas herramientas, los desafíos encontrados (particularmente con el patrón Observer y Fyne), y cómo se resolvieron, con el objetivo de brindar una referencia técnica para otros desarrolladores.

1. Inicialización de Fyne

Fyne es una librería moderna para desarrollar interfaces gráficas con Go. La inicialización básica sigue estos pasos:

  1. Crear una nueva aplicación con app.New().
  2. Configurar la ventana principal con app.NewWindow().
  3. Diseñar el contenido utilizando contenedores y widgets de Fyne.
  4. Llamar a ShowAndRun() para ejecutar la aplicación.

En el simulador, se creó una ventana principal que integra la vista del estacionamiento y se conecta con el modelo de lógica concurrente:

func main() {
    myApp := app.New()
    mainWindow := myApp.NewWindow("Simulador de Parking")

    estacionamiento := models.NewEstacionamiento(20)
    parkingView := views.NewParkingView()

    mainScene := scenes.NewMainScene(estacionamiento, parkingView)
    mainWindow.SetContent(parkingView.Container)

    mainWindow.ShowAndRun()
}

Enter fullscreen mode Exit fullscreen mode

Este flujo básico facilita la separación entre la lógica de negocio y la interfaz gráfica.

2. Uso del Patrón Observer

Por qué usar el patrón Observer

El patrón Observer se utilizó para mantener sincronizadas las capas del modelo y la vista. Cuando un vehículo entra o sale del estacionamiento, el modelo notifica a la vista, que actualiza los elementos gráficos correspondientes. Este patrón es ideal para sistemas donde múltiples componentes deben reaccionar ante un mismo evento.

Problemas encontrados al usar el patrón Observer en Go

Implementar el patrón Observer en Go puede ser desafiante, especialmente para quienes están acostumbrados a su implementación en lenguajes orientados a objetos como Java o C#. Un problema común al usar este patrón en Go es el manejo de concurrencia y bloqueos mutuos al notificar a los observadores.

Inicialmente, al iterar sobre los observadores registrados en el modelo (Estacionamiento) para notificar eventos, se producían condiciones de carrera y bloqueos. Esto sucedía porque el método que registraba nuevos observadores no estaba protegido correctamente, lo que causaba accesos simultáneos a la lista de observadores.

Cómo se solucionó
Para resolver este problema, se utilizó un mutex (sync.Mutex) para proteger el acceso concurrente a la lista de observadores. Además, se implementaron métodos seguros para registrar observadores y notificar eventos:

func (e *Estacionamiento) RegistrarObservador(o Observer) {
    e.mu.Lock()
    defer e.mu.Unlock()
    e.observadores = append(e.observadores, o)
}

func (e *Estacionamiento) NotificarVehiculoEntra(id, cajon, espaciosDisponibles, capacidad int) {
    e.mu.Lock()
    defer e.mu.Unlock()
    for _, o := range e.observadores {
        o.OnVehiculoEntra(id, cajon, espaciosDisponibles, capacidad)
    }
}

func (e *Estacionamiento) NotificarVehiculoSale(id, cajon, espaciosDisponibles, capacidad int) {
    e.mu.Lock()
    defer e.mu.Unlock()
    for _, o := range e.observadores {
        o.OnVehiculoSale(id, cajon, espaciosDisponibles, capacidad)
    }
}
Enter fullscreen mode Exit fullscreen mode

Implementación completa en el proyecto
El modelo Estacionamiento actúa como el sujeto observable, mientras que la escena principal (MainScene) y otros componentes, como la vista gráfica, son observadores:
1. Definición de la interfaz Observer:

package models

type Observer interface {
    OnVehiculoEntra(id, cajon, espaciosDisponibles, capacidad int)
    OnVehiculoSale(id, cajon, espaciosDisponibles, capacidad int)
}

Enter fullscreen mode Exit fullscreen mode
  1. Notificación de eventos desde el modelo:
func (e *Estacionamiento) VehiculoEntra(id int) {
    // Lógica para manejar la entrada del vehículo
    espaciosDisponibles := e.capacidad - e.ocupados
    e.NotificarVehiculoEntra(id, cajon, espaciosDisponibles, e.capacidad)
}

func (e *Estacionamiento) VehiculoSale(id int) {
    // Lógica para manejar la salida del vehículo
    espaciosDisponibles := e.capacidad - e.ocupados
    e.NotificarVehiculoSale(id, cajon, espaciosDisponibles, e.capacidad)
}
Enter fullscreen mode Exit fullscreen mode
  1. Respuesta de los observadores:
func (s *MainScene) OnVehiculoEntra(id, cajon, espaciosDisponibles, capacidad int) {
    s.View.UpdateState(espaciosDisponibles, capacidad, id, cajon, "entra")
}

func (s *MainScene) OnVehiculoSale(id, cajon, espaciosDisponibles, capacidad int) {
    s.View.UpdateState(espaciosDisponibles, capacidad, id, cajon, "sale")
}
Enter fullscreen mode Exit fullscreen mode

Esta solución asegura que las actualizaciones sean consistentes y que las condiciones de carrera no afecten el rendimiento del sistema.

3. Problema Técnico: Renderizado y Cálculo de Posiciones

Contexto

El principal reto técnico fue calcular las posiciones de los cajones en la interfaz gráfica y actualizar su color en tiempo real. Los cajones debían:

  1. Estar dispuestos en dos filas con un espaciado uniforme.
  2. Cambiar dinámicamente de color (rojo para ocupado, negro para disponible).

Problemas Identificados

  1. Cálculo dinámico de posiciones: Los cajones de estacionamiento debían posicionarse en dos filas, con un espaciado uniforme entre ellos. Sin embargo, calcular y actualizar estas posiciones resultó complejo, ya que dependían de coordenadas precisas dentro de un contenedor sin diseño (container.NewWithoutLayout()).
  2. Sincronización visual: Al manejar múltiples hilos concurrentes, surgieron inconsistencias visuales al intentar actualizar los colores de los cajones en tiempo real. En ocasiones, los cambios no se reflejaban o causaban errores gráficos.

Cálculo de posiciones
Se emplearon coordenadas absolutas para definir la posición inicial y el espaciado:

xStart, yTop, yBottom := float32(185), float32(120), float32(200)
spotSpacing := float32(55)

// Fila superior
for i := 0; i < 10; i++ {
    parkingSpots = append(parkingSpots, fyne.Position{X: xStart + float32(i)*spotSpacing, Y: yTop})
}

// Fila inferior
for i := 0; i < 10; i++ {
    parkingSpots = append(parkingSpots, fyne.Position{X: xStart + float32(i)*spotSpacing, Y: yBottom})
}
Enter fullscreen mode Exit fullscreen mode

Renderizado dinámico
Se implementaron funciones para pintar los cajones según su estado:

func (v *ParkingView) DrawRedRectangle(x, y float32, slotID int) {
    if _, exists := v.slotRects[slotID]; exists {
        return
    }

    rect := canvas.NewRectangle(color.RGBA{R: 255, G: 0, B: 0, A: 255})
    rect.Resize(fyne.NewSize(30, 30))
    rect.Move(fyne.NewPos(x, y))

    v.Overlay.Add(rect)
    v.slotRects[slotID] = rect
    v.Overlay.Refresh()
}

func (v *ParkingView) RemoveRedRectangle(slotID int) {
    if rect, exists := v.slotRects[slotID]; exists {
        v.Overlay.Remove(rect)
        delete(v.slotRects, slotID)
        v.Overlay.Refresh()
    }
}
Enter fullscreen mode Exit fullscreen mode

Sincronización Visual
Para garantizar que los cambios visuales fueran consistentes con el estado del sistema, se actualizó el texto de la etiqueta principal y el estado de los cajones dentro de una función central:

func (v *ParkingView) UpdateState(espaciosDisponibles, capacidad int, id, cajon int, accion string) {
    v.Label.SetText(fmt.Sprintf("Espacios disponibles: %d/%d", espaciosDisponibles, capacidad))

    if accion == "entra" {
        v.DrawRedRectangle(v.parkingSpots[cajon-1].X, v.parkingSpots[cajon-1].Y, cajon)
    } else if accion == "sale" {
        v.RemoveRedRectangle(cajon)
    }
}
Enter fullscreen mode Exit fullscreen mode

Esto asegura una representación gráfica precisa y actualizada en todo momento.

Conclusión

Este proyecto no solo logró su objetivo de simular un estacionamiento concurrente, sino que también enfrenta problemas prácticos de desarrollo, como el uso del patrón Observer y la creación de interfaces gráficas con Fyne. Los problemas encontrados y las soluciones implementadas buscan servir como una guía para otros desarrolladores que comienzan con Go o que enfrentan retos similares.
La implementación del patrón Observer en Go, en particular, demuestra cómo manejar la concurrencia de manera segura y eficiente. Este reporte, al documentar estos problemas y soluciones, tiene como objetivo aportar a la comunidad de programadores interesados en aprender y aplicar estas herramientas, facilitando su proceso de aprendizaje y desarrollo.
Si tuviste alguna duda sobre la implementación y la solución de este puedes consultar mi repositorio de github: simulador-parking.git

Top comments (0)