Capítulo 9
S
S
i
i
m
m
u
u
l
l
a
a
ç
ç
ã
ã
o
o
p
p
o
o
r
r
e
e
v
v
e
e
n
n
t
t
o
o
s
s
u
u
s
s
a
a
n
n
d
d
o
o
C
C
Implementação de um simulador por eventos9.
S
S
i
i
m
m
u
u
l
l
a
a
ç
ç
ã
ã
o
o
p
p
o
o
r
r
e
e
v
v
e
e
n
n
t
t
o
o
s
s
u
u
s
s
a
a
n
n
d
d
o
o
C
C
Neste capítulo iremos transferir para código da linguagem C toda a estrutura de um simulador por eventos. Falaremos de como implementar entidades, actividades, eventos e todos os blocos e funções que caracterizam este tipo de abordagem. O código daí resultante poderá depois ser compilado e usado como base para o desenvolvimento de futuros simuladores. Como ferramenta de programação usaremos, para além das bibliotecas comuns do C standard, uma das bibliotecas incluídas no Microsoft Visual C++ 4.0 que já integra a funcionalidade exigida ao manejo de filas de apontadores. Este recurso será extremamente útil para a implementação tanto da lista de eventos como das filas de entidades em geral. Contudo, é importante ter bem presente que não se trata de programação em C++, mas simplesmente de usar uma biblioteca dessa linguagem ultimamente tornada disponível também ao programador de C standard. Para aqueles que preferirem usar outros tipos de compiladores de C, ou compiladores antigos que não incluam o manejo de filas de apontadores, aconselhamos a munirem-se de qualquer recurso que lhes permita substituir esta funcionalidade. No Apêndice 1 o leitor encontrará o código de uma pequena biblioteca de simulação que o ajudará a implementar e manejar tais recursos.
O desenvolvimento do simulador irá ser feito por passos, principiando pela implementação dos seus componentes mais elementares e evoluindo depois em direcção à implementação dos mais complexos. Em cada passo ter-se-á em atenção os aspectos mais relevantes da respectiva implementação nesta linguagem, esperando que o leitor possa seguir as diversas etapas deste processo sem ter de esquecer os motivos que lhe estão subjacentes. É suposto, também, que
o leitor possua já um bom nível de conhecimentos da linguagem C, caso contrário é provável que se veja confrontado com algumas dificuldades. Por outro lado, aqueles que possuam tal experiência e que estejam mais interessados na programação por objectos poderão, sem grande perda de informação, saltar para o próximo capítulo, onde se trata o mesmo assunto em termos de uma abordagem orientada para objectos, usando o Microsoft Visual C++.
Na sequência da leitura deste capítulo, aconselhamos o leitor a munir-se de um compilador de C e seguir, no respectivo editor, os passos que aqui irão sendo dados.
9.1
E
E
n
n
t
t
i
i
d
d
a
a
d
d
e
e
s
s
,
,
a
a
c
c
t
t
i
i
v
v
i
i
d
d
a
a
d
d
e
e
s
s
e
e
e
e
v
v
e
e
n
n
t
t
o
o
s
s
Antes de mais, recordemos que em C standard o tipo de estrutura de dados mais complexo corresponde a um tipo designado por estrutura (struct). Variáveis criadas segundo este protótipo poderão englobar dentro do seu “corpo” outras variáveis de quaisquer tipos, incluindo novas estruturas. Isto torna a estrutura extremamente flexível e, por isso, poderosa. Como veremos, a maioria dos elementos do simulador terão como base variáveis deste tipo.
Outro aspecto que convém não esquecer é que, apesar de este código estar escrito com base nas bibliotecas standad do C, para que se possam utilizar as listas de apontadores deverá também incluir-se na secção de cabeçalho o ficheiro
afxcoll.h. O cabeçalho do programa principal será então o seguinte:
#include <stdio.h> //biblioteca standard
#include <math.h> //biblioteca de apoio matemático #include <afxcoll.h> //biblioteca de manejo de apontadores
Outras facilidades poderão ser mais tarde adicionadas ao programa conforme forem sendo necessárias, contudo, neste momento consideraremos somente o que é fundamental para a implementação do “esqueleto” do simulador, ou seja, para conseguirmos pôr em funcionamento a estrutura básica de um simulador por eventos. E, para isso, comecemos pelo elemento entidade.
9.1.1 AAsseennttiiddaaddees s
Tal como anteriormente foi dito, no capítulo 2, a entidade é um elemento de simulação que possui associados um conjunto de estados e um grupo de atributos. O primeiro diz respeito aos estados por que ela poderá passar durante a execução do seu ciclo de actividades, e o segundo às variáveis de apoio a decisões futuras,
como, por exemplo, se em dadas condições é para se deslocar no sentido de uma actividade ou de outra. O elemento entidade poderia então ser implementado com base numa estrutura que contivesse estes dois tipos de informação. Porém, como vimos nos últimos capítulos, o sistema também pode ser visto como um conjunto de actividades (ou estados) por onde as várias entidades fluem, e muitas dessas actividades são comuns a várias entidades, pelo que poderá considerar-se que o conjunto dos seus estados “pertence” mais ao sistema do que às próprias entidades. Sendo assim, separaremos das entidades os estados e representá-las-emos simplesmente como um grupo de atributos. Para simplificar, uma entidade será então definida por um protótipo do tipo:
typedef struct {
int id; //identificador da entidade int type; //tipo da entidade
float time; //tempo da entidade char* name; //nome da entidade BOOL working; //entidade ocupada float workTime; //tempo de trabalho } ENTIDADE;
Claro que outros parâmetros poderiam ser incluídos nesta estrutura, tais como a cor ou a figura da entidade para o caso de esta ter de ser representada no ecrã, as suas dimensões, etc. Porém, como dissemos, o que neste capítulo interessa é levar o leitor a implementar os mecanismos básicos do simulador, pois a partir deles ser-lhe-á depois fácil acrescentar os restantes artifícios, que são mais do domínio da normal programação do que propriamente da simulação.
Muitas entidades, contudo, necessitam de ser criadas de forma dinâmica, ou seja, durante o decorrer da própria simulação, o que exige a utilização de funções de alocação dinâmica de memória. Por isso, é costume incluir no código do simulador uma função que permita tais facilidades e que, ao mesmo tempo, preencha as variáveis mais importantes da estrutura protótipo da entidade. Uma possível função desse tipo é apresentada a seguir.
ENTIDADE* newEntity(int id, int type, char* name) {
ENTIDADE* nova; char* aux;
nova = (ENTIDADE*) calloc(1, sizeof(ENTIDADE)); nova->id = id;
strcpy(nova->name, name); nova->time = 0.0f; nova->working = FALSE; nova->workTime = 0.0f; return nova; }
Esta função recebe um identificador, um tipo e um nome, e retorna um apontador para a nova entidade entretanto criada em memória. Por uma questão de segurança, os restantes campos da estrutura da entidade foram preenchidos com valores iniciais, devendo mais tarde ser alterados conforme forem sendo necessários. A eliminação da entidade assim criada pode ser conseguida usando a função free() do C standard.
9.1.2 AAssaaccttiivviiddaaddeess
Cada actividade no sistema será aqui representada por uma fila de entidades, não sendo necessário fazer-se, para já, qualquer distinção entre actividades vivas e actividades mortas. Assim, o conjunto dos estados do sistema será representado por um conjunto de listas de apontadores às quais serão dados os nomes das respectivas actividades. Apesar de tal não permitir generalizar o código do programa para um número qualquer de actividades, pois para cada modelo ter-se-á de representar as suas actividades específicas, a verdade é que desta forma se simplifica a apresentação deste código, levando o leitor a compreender com mais facilidade os meandros do programa.
Supondo, então, que temos N actividades no sistema, e que o tipo de variável a que corresponde a lista de apontadores é designado por CPtrList, há que incluir no cabeçalho do programa as seguintes declarações:
CPtrList Actividade1; //actividade 1 CPtrList Actividade2; //actividade 2 //…
CPtrList ActividadeN; //actividade N
Como é óbvio, tratando-se do caso da barbearia, por exemplo, em vez destes nomes a primeira actividade poderia ter sido designada por Chegada, a segunda por Espera a terceira por Corte e a última por EXTERIOR. Ou de qualquer outra forma, desde que se tenha em conta que cada uma delas é implementada na forma de uma lista preparada para receber entidades.
9.1.3 EEvveennttoosseelliissttaaddeeeevveennttoos s
Um evento, como se sabe, representa um instante da simulação em que se verifica uma transição de estado no sistema. Por outro lado, o evento é normalmente associado a uma determinada entidade. Por esta razão o elemento evento será aqui representado por uma estrutura que inclui um tempo e um apontador para uma entidade. Para além disso, para que o evento possa ser identificado na simulação, inclui-se ainda nessa estrutura uma variável a que normalmente se chama identificador do evento. Tal estrutura passa então a ter a seguinte forma:
typedef struct {
int id; //identificador do evento float time; //tempo do evento
ENTIDADE* entity; //entidade associada ao evento } EVENTO;
A partir de agora, ao falarmos de evento, estaremos a referir-nos a uma destas
estruturas. Por isso, a lista de eventos da simulação será uma lista que conterá
apontadores para elementos deste tipo, a qual pode facilmente ser implementada através da seguinte declaração:
CPtrList eventList; //lista de eventos
A lista de eventos é, então, do tipo das listas anteriormente usadas para implementar as actividades, só que, em vez de apontadores para entidades, irá conter apontadores para eventos.
Ainda sobre os eventos, há que ter em conta que se trata também de elementos dinâmicos, sendo a maioria deles criados e, por ventura, aniquilados, durante o decorrer da simulação. Por esse motivo, e como acontecia no caso das entidades, será necessário desenvolver uma função que permita tais facilidades, o que pode ser conseguido com o código que a seguir se apresenta.
EVENTO* newEvent(int id, float time, ENTIDADE* ent) {
EVENTO* novo;
novo = (EVENTO*) calloc(1, sizeof(EVENTO)); novo->id = id;
novo->time = time; novo->entity = ent;
return novo; }
Esta função recebe o identificador de um evento, um tempo e um apontador para uma entidade e retorna um apontador para o novo evento entretanto criado no sistema. Uma vez mais, a qualquer momento poder-se-á eliminar esse evento usando, como é sabido, a função free() do C standard.
9.2 SSoobbrreeaasslliissttaassddeeaappoonnttaaddoorreess““CCPPttrrLLiisstt” ”
Antes de passarmos à implementação dos restantes componentes do simulador convém falar um pouco mais das listas de apontadores do tipo CPtrList. Sem pretendermos entrar no conceito de classe da programação orientada para objectos, poder-se-á dizer que este tipo de listas possui já certas funções associadas que permitem manusear a informação nelas contida. Assim, torna-se extremamente simples adicionar-lhes novos elementos, quer à cabeça quer ao fim, retirar delas elementos, executar buscas indexadas ou por apontador, e, de uma maneira geral, realizar todas as operações características do manejo de uma lista.
Para maior eficiência dos processos de iteração ao longo da lista, é vulgar utilizar-se uma variável do tipo POSITION1 como argumento de algumas destas funções, o que permite executar certas operações com mais rapidez do que através da indexação normal. Uma variável deste tipo é normalmente usada quando se pretende percorrer o conteúdo da lista, por exemplo. Conforme for entrando no código do simulador, o leitor ir-se-á apercebendo das potencialidades de tais recursos. Para já, fiquemos com uma explicação sucinta de algumas dessas funções, supondo, para isso, que antes foi criada uma variável do tipo CPrtList
chamada lista.
lista.GetHead() Retorna o elemento da cabeça da lista (a lista não pode estar vazia).
lista.GetTail() Retorna o elemento do fim da lista (a lista não pode estar vazia).
lista.RemoveHead() Remove o elemento da cabeça da lista.
lista.RemoveTail() Remove o último elemento da lista.
lista.AddHead() Adiciona um novo elemento à cabeça da lista.
lista.AddTail() Adiciona um novo elemento ao fim da lista.
lista.RemoveAll() Remove todos os elementos da lista.
lista.GetHeadPosition()Retorna a posição POSITION do elemento à cabeça da lista.
1 POSITION é um tipo de variável usado para iteração de listas de apontadores declarado no ficheiro “afx.h” que
“afxcoll.h” indirectamente utiliza. Por isso, é importante que o primeiro ficheiro seja também disponibilzado ao compilador.
lista.GetTailPosition() Retorna a posição POSITION do último elemento da lista.
lista.GetNext() Retorna o próximo elemento para iteração recebendo como parâmetro
uma variável POSITION. No final da operação esta variável é incrementada de uma posição.
lista.GetPrev() Retorna o anterior elemento para iteração recebendo como parâmetro
uma variável POSITION. No final esta variável é decrementada de uma posição.
lista.GetAt() Retorna o elemento de uma dada posição da lista, recebendo como
parâmetro uma variável POSITION.
lista.SetAt() Introduz um elemento numa dada posição da lista, recebendo como
parâmetro uma variável POSITION.
lista.RemoveAt() Remove um elemento da lista de uma posição específica, recebendo
para isso uma variável POSITION.
lista.InsertBefore() Insere um novo elemento antes de um posição específica,
recebendo para isso uma variável POSITION.
lista.InsertAfter() Insere um novo elemento depois de uma posição específica,
recebendo para isso uma variável POSITION.
lista.Find() Retorna a posição de um elemento especificado por o seu apontador.
lista.FindIndex()Retorna a posição de um elemento com base numa indexação.
lista.GetCount()Retorna o número de elementos da lista.
lista.IsEmpty() Testa se a lista está vazia, retornando uma variável boleana.
A partir destas funções é fácil manejar quer as entidades nas listas que representam as actividades no sistema quer os eventos na lista de eventos da simulação. Informação mais detalhada sobre este assunto poderá ser encontrada no manual, ou no help on line, do Microsoft Visual C++.
9.3
M
M
a
a
r
r
c
c
a
a
ç
ç
ã
ã
o
o
d
d
e
e
e
e
v
v
e
e
n
n
t
t
o
o
s
s
A marcação de um evento na lista de eventos da simulação é feita através da chamada a uma função do simulador normalmente designada por Schedule(). Esta função recebe como variáveis de entrada o tempo em que o evento deverá ser executado, o identificador do evento e um apontador para a entidade a ele associada, e é depois responsável por criar um novo evento no sistema, evento esse que esta mesma função inserirá na lista de eventos por ordem cronológica de execução. No caso de o tempo desse evento coincidir com o tempo de algum evento nessa lista, a posição do novo evento passará também a depender da prioridade que lhe tiver sido previamente atribuída. Essa prioridade é aqui considerada maior quanto menor for o valor atribuído ao identificador (id) do evento. A função que a seguir se apresenta cumpre todos estes requisitos.